From 430ac5d89f7dc85768aa7218d6d440c73e01443d Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Fri, 15 Jul 2022 00:47:32 +0200 Subject: [PATCH] Removed gaborator source, replace with submodule --- .gitmodules | 3 + lib/gaborator | 1 + lib/gaborator/CHANGES | 99 - lib/gaborator/LICENSE | 11 - lib/gaborator/README | 1 - lib/gaborator/doc/agpl-3.0.txt | 661 ---- lib/gaborator/doc/doc.css | 53 - lib/gaborator/doc/filter-response.png | Bin 19915 -> 0 bytes lib/gaborator/doc/filter.html | 266 -- ...ls_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png | Bin 124422 -> 0 bytes ...ls_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png | Bin 109213 -> 0 bytes .../grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png | Bin 14556 -> 0 bytes lib/gaborator/doc/index.html | 78 - lib/gaborator/doc/overview.html | 135 - lib/gaborator/doc/realtime.html | 126 - lib/gaborator/doc/ref/gaborator_h.html | 462 --- lib/gaborator/doc/ref/intro.html | 41 - lib/gaborator/doc/ref/render_h.html | 74 - lib/gaborator/doc/render.html | 395 --- lib/gaborator/doc/snr.html | 121 - lib/gaborator/doc/spectrogram.jpg | Bin 22542 -> 0 bytes lib/gaborator/doc/stream.html | 279 -- lib/gaborator/doc/synth.html | 213 -- lib/gaborator/examples/filter.cc | 69 - lib/gaborator/examples/render.cc | 69 - lib/gaborator/examples/snr.cc | 32 - lib/gaborator/examples/stream.cc | 72 - lib/gaborator/examples/synth.cc | 62 - lib/gaborator/gaborator/affine_transform.h | 42 - lib/gaborator/gaborator/fft.h | 29 - lib/gaborator/gaborator/fft_naive.h | 191 -- lib/gaborator/gaborator/fft_pffft.h | 214 -- lib/gaborator/gaborator/fft_vdsp.h | 209 -- lib/gaborator/gaborator/gaborator.h | 2966 ----------------- lib/gaborator/gaborator/gaussian.h | 123 - lib/gaborator/gaborator/pod_vector.h | 116 - lib/gaborator/gaborator/pool.h | 47 - lib/gaborator/gaborator/ref.h | 80 - lib/gaborator/gaborator/render.h | 506 --- lib/gaborator/gaborator/resample2.h | 324 -- lib/gaborator/gaborator/vector_math.h | 301 -- lib/gaborator/gaborator/version.h | 15 - 42 files changed, 4 insertions(+), 8482 deletions(-) create mode 160000 lib/gaborator delete mode 100644 lib/gaborator/CHANGES delete mode 100644 lib/gaborator/LICENSE delete mode 100644 lib/gaborator/README delete mode 100644 lib/gaborator/doc/agpl-3.0.txt delete mode 100644 lib/gaborator/doc/doc.css delete mode 100644 lib/gaborator/doc/filter-response.png delete mode 100644 lib/gaborator/doc/filter.html delete mode 100644 lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png delete mode 100644 lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png delete mode 100644 lib/gaborator/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png delete mode 100644 lib/gaborator/doc/index.html delete mode 100644 lib/gaborator/doc/overview.html delete mode 100644 lib/gaborator/doc/realtime.html delete mode 100644 lib/gaborator/doc/ref/gaborator_h.html delete mode 100644 lib/gaborator/doc/ref/intro.html delete mode 100644 lib/gaborator/doc/ref/render_h.html delete mode 100644 lib/gaborator/doc/render.html delete mode 100644 lib/gaborator/doc/snr.html delete mode 100644 lib/gaborator/doc/spectrogram.jpg delete mode 100644 lib/gaborator/doc/stream.html delete mode 100644 lib/gaborator/doc/synth.html delete mode 100644 lib/gaborator/examples/filter.cc delete mode 100644 lib/gaborator/examples/render.cc delete mode 100644 lib/gaborator/examples/snr.cc delete mode 100644 lib/gaborator/examples/stream.cc delete mode 100644 lib/gaborator/examples/synth.cc delete mode 100644 lib/gaborator/gaborator/affine_transform.h delete mode 100644 lib/gaborator/gaborator/fft.h delete mode 100644 lib/gaborator/gaborator/fft_naive.h delete mode 100644 lib/gaborator/gaborator/fft_pffft.h delete mode 100644 lib/gaborator/gaborator/fft_vdsp.h delete mode 100644 lib/gaborator/gaborator/gaborator.h delete mode 100644 lib/gaborator/gaborator/gaussian.h delete mode 100644 lib/gaborator/gaborator/pod_vector.h delete mode 100644 lib/gaborator/gaborator/pool.h delete mode 100644 lib/gaborator/gaborator/ref.h delete mode 100644 lib/gaborator/gaborator/render.h delete mode 100644 lib/gaborator/gaborator/resample2.h delete mode 100644 lib/gaborator/gaborator/vector_math.h delete mode 100644 lib/gaborator/gaborator/version.h diff --git a/.gitmodules b/.gitmodules index f604849..72ddb71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/MIPP"] path = lib/MIPP url = https://github.com/hayguen/MIPP.git +[submodule "lib/gaborator"] + path = lib/gaborator + url = https://git.gammaspectra.live/S.O.N.G/TheGaborator.git diff --git a/lib/gaborator b/lib/gaborator new file mode 160000 index 0000000..80ef6d4 --- /dev/null +++ b/lib/gaborator @@ -0,0 +1 @@ +Subproject commit 80ef6d4c8703d165660f7ef8d321fce9b526adfc diff --git a/lib/gaborator/CHANGES b/lib/gaborator/CHANGES deleted file mode 100644 index 3294dc5..0000000 --- a/lib/gaborator/CHANGES +++ /dev/null @@ -1,99 +0,0 @@ - -1.7 - -Miscellaneous bug fixes. - -Support lower numbers of bands per octave, down to 4. - -Further improve the performance of analyzing short signal blocks. - -The "Frequency-Domain Filtering" and "Streaming" examples now use -a white noise and impulse signal, respectively. - -1.6 - -Add "API Introduction" documentation section that was missing -from version 1.5, causing broken links. - -Improve analysis and resynthesis performance when using PFFFT or vDSP -by automatically enabling the use of real rather than complex FFTs -where applicable. - -1.5 - -Add navigation links to the HTML documentation. - -Add a code example demonstrating synthesis of musical notes. - -Add a function process() for iterating over coefficients sets with -greater flexibility than apply(). Also add a function fill() for -algorithmically creating new coefficients. - -Make the C++ declarations in the API reference documents more closely -resemble actual C++ code. - -Add a method gaborator::analyzer::band_ref() returning the band number -corresponding to the reference frequency. - -1.4 - -Support building the library as C++17, while retaining compatibility -with C++11. - -Further improve the performance of analyzing short signal blocks, and -of signal blocks not aligned to large powers of two. - -Add a code example mesasuring the resynthesis signal-to-noise -ratio (SNR). - -1.3 - -Eliminate some compiler warnings. - -Declare gaborator::analyzer::band_ff() const, making the code match -the documentation. - -Fix incorrect return type of gaborator::analyzer::band_ff() in the -documentation. - -Improve performance of analyzing short signal blocks. - -Remove special-case optimization of analyzing signal slices of all -zeros, as it caused incorrect results. - -Support up to 384 bands per octave. - -1.2 - -Add overview documentation. - -Add real-time FAQ. - -Actually include version.h in the release. - -Fix off-by-one error in defintion of analyzer constructor ff_min -argument. - -Fix incorrect return value of band_ff() for DC band. - -Add streaming example code. - -Add analyzer::analysis_support() and analyzer::synthesis_support(). - -Document analyzer::band_ff(). - -Improve signal to noise ratio at low numbers of bands per octave. - -Note the need for -mfpu=neon on ARM in render.html. - -1.1 - -Added CHANGES file. - -Added reference documentation. - -New include file gaborator/version.h. - -1.0 - -Initial release diff --git a/lib/gaborator/LICENSE b/lib/gaborator/LICENSE deleted file mode 100644 index abcfc9f..0000000 --- a/lib/gaborator/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ - -The Gaborator library is Copyright (C) 1992-2019 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 -licensing under different terms is possible. Please contact -info@gaborator.com for more information. diff --git a/lib/gaborator/README b/lib/gaborator/README deleted file mode 100644 index 582593b..0000000 --- a/lib/gaborator/README +++ /dev/null @@ -1 +0,0 @@ -See doc/index.html for HTML documentation. diff --git a/lib/gaborator/doc/agpl-3.0.txt b/lib/gaborator/doc/agpl-3.0.txt deleted file mode 100644 index be3f7b2..0000000 --- a/lib/gaborator/doc/agpl-3.0.txt +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. - - - Copyright (C) - - 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 . - -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 -. diff --git a/lib/gaborator/doc/doc.css b/lib/gaborator/doc/doc.css deleted file mode 100644 index 86aab82..0000000 --- a/lib/gaborator/doc/doc.css +++ /dev/null @@ -1,53 +0,0 @@ -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: 2em; -} -h3 { - 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; -} -pre.forward_decl { -/* Needed for syntax checking, but avoid clutter for human readers */ - display: none; -} -div.class_def { - margin-left: 2em; -} -div.nav { - margin-top: 30px; - font-style: oblique; -} -div.nav span.prev { - float: left; -} -div.nav span.next { - float: right; -} \ No newline at end of file diff --git a/lib/gaborator/doc/filter-response.png b/lib/gaborator/doc/filter-response.png deleted file mode 100644 index 7be505e9911c864b2c7209a55767c1b5643ee9f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19915 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 diff --git a/lib/gaborator/doc/filter.html b/lib/gaborator/doc/filter.html deleted file mode 100644 index 25aa939..0000000 --- a/lib/gaborator/doc/filter.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - -Gaborator Example 2: Frequency-Domain Filtering - - -

Example 2: Frequency-Domain Filtering

- -

Introduction

- -

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.

- -

The specific filter implemented here is a 3 dB/octave lowpass -filter. This is sometimes called a pinking filter 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. -

- -

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

- -

Preamble

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

Reading the Audio

- -

The code for reading the input audio file is identical to -that in Example 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: "
-            << sf_strerror(sf_in) << "\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);
-
- -

Spectrum Analysis Parameters

- -

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.

-
-    gaborator::parameters params(100, 20.0 / fs);
-    gaborator::analyzer<float> analyzer(params);
-
- -

Precalculating Gains

- -

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 band_gains -of one gain value per band, including one for the -special lowpass band that contains the frequencies from 0 to 20 Hz.

- -
-    std::vector<float> band_gains(analyzer.bands_end());
-
- -

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 -analyzer method band_ff(), which -returns the center frequency of the band in units of the -sampling frequency. The gain is normalized to unity at 20 Hz. -

-
-    for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) {
-        double f_hz = analyzer.band_ff(band) * fs;
-        band_gains[band] = 1.0 / sqrt(f_hz / 20.0);
-    }
-
- -

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.

- -
-    band_gains[analyzer.band_lowpass()] = band_gains[analyzer.bandpass_bands_end() - 1];
-
- -

De-interleaving

- -

To handle stereo and other multi-channel audio files, -we will loop over the channels and filter each channel separately. -Since libsndfile produces interleaved samples, we first -de-interleave the current channel into a temporary vector called -channel:

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

Spectrum Analysis

-

Now we can spectrum analyze the current channel, producing -a set of coefficients:

-
-        gaborator::coefs<float> coefs(analyzer);
-        analyzer.analyze(channel.data(), 0, channel.size(), coefs);
-
- -

Filtering

-

-The filtering is done using the function -process(), 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 int64_t argument is the time in units -of samples; this could be use to implement a time-varying filter if -desired.

-

-The second and third argument to process() specify a -range of frequency bands to process; here we pass INT_MIN, -INT_MAX to process all of them. Similarly, the fourth and -fifth argument specify a time range to process, and we pass -INT64_MIN, INT64_MAX to process all the coefficients -in coefs regardless of time. -

-
-        process([&](int band, int64_t, std::complex<float> &coef) {
-                coef *= band_gains[band];
-            },
-            INT_MIN, INT_MAX,
-            INT64_MIN, INT64_MAX,
-            coefs);
-
- -

Resynthesis

-

We can now resynthesize audio from the filtered coefficients by -calling synthesize(). This is a mirror image of the call to -analyze(): now the coefficients are the input, and -the buffer of samples is the output. The channel -vector that originally contained the input samples for the channel -is now reused to hold the output samples.

-
-        analyzer.synthesize(coefs, 0, channel.size(), channel.data());
-
- -

Re-interleaving

-

The audio vector that contained the -original interleaved audio is reused for the interleaved -filtered audio. This concludes the loop over the channels. -

-
-        for (sf_count_t i = 0; i < n_frames; i++)
-            audio[i * sfinfo.channels + ch] = channel[i];
-    }
-
- -

Writing the Audio

-

The filtered audio is written using libsndfile, -using code that closely mirrors that for reading. -Note that we use SFC_SET_CLIPPING -to make sure that any samples too loud for the file format -will saturate; by default, libsndfile makes them -wrap around, which sounds really bad.

- -
-    SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo);
-    if (! sf_out) {
-        std::cerr << "could not open output audio file: "
-            << sf_strerror(sf_out) << "\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);
-
-
- -

Postamble

-

-We need a couple more lines of boilerplate to make the example a -complete program: -

-
-    return 0;
-}
-
- -

Compiling

-

Like Example 1, this example -can be built using a one-line build command: -

-
-c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` filter.cc `pkg-config --libs sndfile` -o filter
-
-

Or using the vDSP FFT on macOS:

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

Or using PFFFT (see Example 1 for how to download and build PFFFT):

-
-c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` filter.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o filter
-
- -

Running

-

Running the following shell commands will download an example -audio file containing five seconds of white noise and filter it, -producing pink noise.

-
-wget http://download.gaborator.com/audio/white_noise.wav
-./filter white_noise.wav pink_noise.wav
-
- -

Frequency response

-

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:

-Frequency response plot - - - - - diff --git a/lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png b/lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_anl_wob.png deleted file mode 100644 index b75fa6c368f0a28f89c81cec46c35aa97363ef08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124422 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
yefKZv5z=5818o!VVWskD!4Vdy z7Q6H`JL&wMm0Oae(3-{y_G%U;s-E@Y;Ob1&l+_x!zCx zO{m!AP@r3_Pa>szJ+7zcwY|uQo4=gYQ6n_Baq}Hu?PQqaCyGh(zCL9nLgDYlknZxs z9&E=8Vif+@+!;!YL!*N3z|EnxjYf>)zolaDga3vW@vh|PPg4Dm zSL>zlxlPxNR(apQMwRMO+?propXjWnqf57>-8qn-A9MTv1n8@{i6F4zhPm$2e> zk>+@1eZ$E%`Wj}CBmlgxV#MnXe>NFCH2RB6Q4zR(vGZR- z-(NANWd44|5RsP5!gC&LDc1>&NiQzwlI*v*bZf=D&kmPD=3xd2*1!0elj`Cu+n~}F zeHq~`>D@rV`sTzwH4Gff@%FN3ulkw(Sh z0$B!V%kN)+D;>9;!(Wl>+e4Q44ppJ@1_j>%z{OVeY(AKc@f<@tDGO-*A-%;8nn7+< zj+;-t|CnDlMH-IQlRse)alL75q)vk#YVeByu(t?e@R!1qQJF;1cQ2j3sT^d1OoCKU z;^mJ&{p2QoPBR5W+JLG`#FWryqINCML*PFiIk69EaiH)Xun;|(hRea{h1=feCVTH8$H5&!}5 z9kr<*W`N4wjZ~mY&2Vz8d&wuvPpX=k@DTKKps(Fy=6}#mn(%LAkcugPPH^QGnb$XO z;UC>LrvXttl_K&Cm~}zAxV_76$HWd1QIgswsHD3`4)84iWzqXex*<+O8T}dY>ZLyg z5QlInObEch#y&L__Q|~1HNb{1NkG#OIOs8wd5X#5pwg^0^;_X@yk||m?4Jx!ZglBS z3r20d&{W9j&`G&nQ&KxQjXXc!$CrytZG)G(&-KQ`G1o3YzRdA=Z`sH^81-Mo!{=U@ zRc{Jl(~Rk8H81*Q;4-Zqqvy5CA%&9C3Eos`aZ$FOH zl!%UuSlnrsr~Pii0w9D!G&L{i?xVhJuC>K+jEdTK<>~45X~mS_8$qtit~PwZTDc8Rk z$Sj@G4dwSFO8wy#py$2}UucUo`z+nr0duFbr8||x@um1Fp#=^B!{umT=wR;tx7t9ai(t2Odo z&Mu}(XvAVDMyMo18RzpG7TX@XjELX!nHrLL9hpV?p_muD7^SMAP81!^ofN&JGFKrz z3?}zQ2Vo^B5r9>kGjp*tT)}#37sQ&ZXZRaMC|xK%Bu%f!eNBb5?(=gdJP>*nJo8f$ z2Zrm@TBQ|}JW-Ok&`cm=>UMe)n>->jT$X;N>@Hxv$Un#~R*Opf%gTY2U+9Ls#|mj! zCs1t&}l|^^Qn9k0g`3;&I9%hvRDSgZkC#XiOnWy@N~cT;3f~ zH!wmUfNpts7mNTmLKl!Iy(Kj&st=ta4~ul<3}U>K!_>*pk7xn->_1$7{Y`B)aCLd@XzYpZEUPTw|m9YDkOL z+(z6?e10P(fSOb+X*D35A`=i{=e%E@s)`%-?MLek+Lxuohl!o)t&vn8=Omd-BwR|hkza1RY(z5UzcMIyBq@5!Z94~HK71rzbcwnD8Er^!u3=rlyD$D@(S-Y zC<^^Z{C>;}%2l>#2pnLxrQ^i0p+Xe5rHFGc(h=rKbH1M;CtMC5n_jdf zoo|G-O!y^zuwtT20yg-PP(JL}|8-AN!Z3SkA>>DXN-tOUU*{l=gk*0{(O>+k`W`6o zRz~XTS*DT>UnVdC*ipvocj~jJPGv9JCQb89k#l!&7T-!SvPFr->gF zI?b_H8`|Kom^zfuzY;4K%n0dj@!P@Hnv+=3&7^yrtvS=eIVuunsi+Hu0~?a+w4#iK}^_+QZCowb!^^ z{&gR9Ctu=fI(e;lRV6p3+l<{}0p;0pM6 z&^^hvBIL@K`eOB$Q`v>=RZaG(t7DTdsUHMlqrt3AK@~SUqFuQ~axR5?H$#_L%|u>% zDZK!&Hfbyw76ff34@SZ<{(!m9)d_Hn4f&Dp2EB{j0*_5a&7R=PDLqZN2nP>i{w~YX zhXUZB9eXdReq$7iD}`x4?Z*RXXuxzan(os(a8UNYBE8&gDI93Wt|KUjHh-W-Ob0g( zY;9(}>MAG-QhB`?b#X5ci_PAtaKLQ?tuf6-d)~)GFkdVwBfJ#pW}7S=C0PaTYe{Fisu$Q##wx(JMn_OQDlB0D0Cdkg$;1%VVcd-mdX z**l`vrkr>%Y7+)hhfZ!t@IqgIba;AHK`CW)3rKBF>8=D(RP*WML?W?{0l@TzjK;$g82{lu9agOh{ z=umbt)LBS~I&#FkA|EC@vQG|c+ok+LSt$JZ5Ai|tTP;^!vQ#H^883JQ!BB}LITRwS1y}T~3Gpsp8P2oD-=?uymy7nr z1zJr!`KQy1`%zzF$qliLtxhAajgOg;rv+2_mLV%dnw?N@CW_jPQ;=AR0fv;rXOb=H zb<53EF=3nlp1L_VYt?7Auv=yu6qw3XDA%?6PDH%;hd6UdV#7$2DC=}%{->dWRnc7) zqL{a_C_?^MD`)ug|Beu!9fbIl_Px`uzoDQI_aZqJfIS7Tj9XSrF;OVO!b2iLG#r>K zVr4@UVT79ESV@50ep`=88&|zm^UPqFj@{vG`@RA=%wq(^`Zw{1Z?6QZjse($Nzs`pOhk=sIIBo=;i#KDvTFigEpK z1m~ZyQ;k3ZlHR`l7AOr_s+*4Kfq*5tP{iy5 z3MWb@5S4KSs#G0z<3D4f_R9)?rKcW)Y$-@s%SL>2;J?* zZ4V0SoyE)2G&bZt(n4a{|A>MNEwKIC3p_3A6VdY~8SMROa!PxTHNXMfuI*jXpTu%o zP7CHJS~WSMnhs(gBy;Bg@5fM3X2Jn?Ac^DOER)8qVc(M)BGt<09h(LxhzL)<8(eEC z^F~kdU2S$`&nn=B*+7~LURw}!{oJ{b`L1ZE^|@6?TjIs|4Pb+VzVwNFV^P|-O^*Rh z=vj}v#01uOx;taEb_ZRO-JT#5hf3X_k+(h9>5~p39a}JfqJTjZLAIq53e1i`XcEk! z4!J1V@Jb4owIYo0OEIAH2#5M9XN8F~Ujv;N$NvbHc3U`gT2Tj8!SKk74qrSfISeC! z!c!qPpAT*s9O-ld1I9}q0me3B!9Z+-0uPXQG;&Mjl4K-C-gqVyOahrKi)DW`(nRv> z*L{av-IJibA$)8_OX<?)3;C&$_w35k-Wk01epQrm z#YERHjZ{p@0PS0oKtM?=8TsAPwv&IgF|8+J0&k1m%;!tN|mm4c2MMz z?T-?n5QS0^Vb0Jd<+{;ID3cP-p0W?jyDqdF*VzEY@Yh{X1ILnv9w&4yEqy$+dW}Ch z1`yu)YA%AHp|mfS>7jc5{8N&m1705)9$u~4EB@HbFyjqGzBMHw zJ!Le^PNHQD#7=ul?fQ(Y_CH$eMj%*+Mm9+Uo=mae1Z%W)W)zEgDGBvei3__0vW8dk zuPbg-=|#csnlN|KmY^!-I4^yK9#!Epwfdvp#6+4coF7XLeXdm>MI*HB*jUKWB`&Co z^vp~oYtl>Y`auLlmf=Ur`~rZFPpYh&Q|dN_|6MlggqT0c<~Q??PO@xFz%!#^<&g*y>= z7q(>HnlKg=c5nXNx8!2J(!WlflFZc3QSCX;>iaY@&c>sA!1YGMdW<}yx4m2*Er_m& zo!ofr>4UND8F*N15Qmc8$_8^F@9hy9%i4jH;Fgh}sk|5V5HLRI0d%o%Cmt_Wp+|rl zGyU%naY_jKy7bL&t3!1{E%k@_qdi16c=*dRFplu?bB=6QwEL5b+?qkb2(iojY-lu% zOXvvVWJidL1JYRvPmuiLYfjV-m|NK~wnSC&?Z(kdrC9J#)vj{rAB6p9RvQ-a8g z+*p=p9?Z^hx1b-Y@J4OnBU9c>!@3VA@6tlKy1qF>kE{{!8Obzu2QJiKuN^L{ z9=Ah{ot*7Mm;4BO_D0UMXB5jmDBPs%*qity_o)VM*`@gQ7jgSG(wtG(%zi{$uhnSZ zNDrBKMOHZ@qBrfg_Z}OFwdOM>wU5dIq+bSTbFtFMgZ1pY-eo#7vD1v!TLw+x9s4i- z984KO&6wcb{;IAVlXrm<_KFmiY5v8#g5q1Jimdl&Yd5>BsbGPG4=2c zd6ON*vg>E2G})Q@10wtYLIa*tn?UR{%kS%<+gZBn^Embqlex>|cf4|cUe1ys7@HWE z_;&DPfpMfS=}q;Bp@6DHCo4SnF}R-^_8b$DTw zcj1yT$CCO<`Y6jA?hOf4+$8HNM0Ayvl^DL0kb?lJI+#>fWBszIu&m z$!ym}o(uGcJKrJFgb{d&Rlo~T;#z5EF+fDgSn8Vzk^}OD?K0{bT1&CcyStzi@52@9wmq+4pa%K z9%(yi4j1;zffvp{60I|?`qfkq`nNs8pkq<1RGlP8wnbJuiuBUp@)Nzrw8hD{Q2NOA z6RUT&c$}AFcepc&Wu)EOKc!3C?BJI((EhC80k*zhaGbq3{6Tco+taERcLpYLBHf&O z*9Y@TwW|whO;e`bgHv6)=N$0wihvoW*^lRGPu6e#I4cc!q2Z7C7J2$hAs4(fVDl@_ zWeYtE+?6{9WenqU?-XhTeXUoN-;3s;V^^Z~x(B1ZZA)U)d`ZePX#O2=zJo6N2xxi? zU+^t*hn)|60UZ;rqSC#x!uRyp8d-vwl+JQ1I3S3gLB@2u$+O>vYpg>9ysH|ggOHi5vb z+!H07V$Vy~>VyKxtI4Ut6UB(?Sd@qoj+|qKzR8tgW#5kdig<)rNCe1qgx18NVxYb^ z?gPKiQh^Jck)PV78EB87P z=cGMFgG?RP%~6%ufKA?`=cTqFJx@Wm^$qz^(qBT-Dk;w?(FJNv6aJs##}jx zlnW&81r_~>fDzJ@*vs$T1ET3r?NX;i&>J}dWOvCGrj%obCc~Ra77UHXq*n5pVmO{~zs)=d>NNcP!R-&7gPr9b5~{9zYW89d*P!tK(}i z_p(5&7$eRip$|i9bcgzkeJ|%n(-4BG69^77c}H8uX>`+w9kY9?=LBnSMzmj98N7T1 zA1~vF`5E7^O|Qwz#h>p4_c+|;ta7kmVy3w(RMx|suu>{aYcO`{_EpwvhResYP+7;) zcz>a`o^4#y+HyemituMWPj1EO2y<>?BO~;p<9cJdA1rnz;65uZoH9D<@`jY}Zb->tFDhN-({le<2)d-S^(o2gPtrG!4sbt zQV^0r1b{BGF?t`pl0z1}&iiJZK0JmHQ?)A)6~yBD6rCMQHKW4Ng+f(H(L%Rd?auAVdVe!jkb_??94qqjh2*xt`uD3l);00qcs3LRy!E$!OK zG=<&pTGS~CN$`D;6vFu*oBzp{*!hqGCtZ$mAaqpakaqrfNM>}o^EplFbmV1q>gOa& z=SwH6vo3gBB`p>2GPw_-e`m-sG?N6s zrgVGdG*Tm^`CY^lGM!wst5bBNOO!eyOsK$!Bo#&&dq^}sB<*cL5-ht_fYo2IEAuUg z08wuI1OmG*SqMYiN)^rcP^YXfH<@O)n%`zzf*+qPz_VZ3Or}Axjg|&!SH?X?p*8>F z%Q1bA)?8C}1D=8Kd94@$Kw~d%E&247hI$5}p;cg%3P@?SEgN zHh#<_Ga(#!?t!^sS6K!>K)+}+h~MzxyUZK0xcye#kC`G!vDsBGUUUesFpUfWR&6kim4mAz@ zv{XaVZ0V1__kPl0p5R^1`rF_?wr2-y2cNH3E??l{LX*xwm|XZbzt4YL*m}tP9zy2k z*&S{*wRp^vblNA*Halz(%$afGo$4p*B#WXvhEN?9_L-)SK|8MkyO>WC2~+KpmcweZG6C?XIWXvh^P4;8xT-d{#o5Iu;?@f( zR_L`tEYjit`S~I^sF`JWpK0<9)D4JJldhZ;1&W=)UDTS|ucd>n)>$J2{4O9{rE%Hj zhyD(he0E$q*6!8bx$SkK2=E;X!wS zh+z+Us2{`b&)XSjVqFtBoJHWoXQ*Do9n$7?c2QzKFmHrzF3?sZ*Y|5?lxG) z@_Q*6&WMA?myLA~m^}A!x@kCBI4;bFUf>4iXanoS^8*)`Iml!c*G&}((GsAGk|-h6 zCe(hJen8uHVzIKJENssYM*N|+yQrRU@a@~%V;Irtj*D8nE>9>k$b$OHB9icIRro1M zreV~dK#4d$7ziV=BKD**{2&IPmsjApDRV$D8Ki|fSbEg*>t^i5Yw_Ruh9dL57t`c8 zPVFl`#RtcoSpl>FFw&jM50`rS*hOn;Hp)kCpjCYwjym9}PqHEFzm1J^Rsyjb}kDB6u zP@Q>a zSbBUlr3z$Ib-!^6>Y|M}-q=MZqKi{{&OY2)sr6;2y7J>XBaW>;pITKu|N6@B<21P2 zqVWPkPkWRhk?j9|E3Z1sMsGYt-qT;M^}F9n@_T=7Pmg(RM;-zW{xk0Hq2EIe6#n*p z-B;ZhHgluylkE;KwoRyy@I>+eMClgDh)xqQ)EDmJQ^Ya5E1;!twG{3F2YS2=>hMGy4IiaYrZ|1 z-Ae8Gha!pSi{R?w%-q>filC^YH_yY`Tz;kl$ngG6fG9jo;W=aQ@bIv6bR_-%G{ZT^ z|9*b@J00}j`PU>+C2BGrA2^GT^1h^`l_fHlXKQtc#9n#h88cF|tVGjsf*(kxyF=WF ze0>Cj13`CIh|&gkU7Frq z4rEyTKsdz;ueaD8(1TQa zYBd*k_}()^>BE7DsCxw0ePq;2kX8&j0w5j8^Ui|>5Vs-;4Zf~!?EAu> zSbPcOsDE}nW&>1QR$W%yCXD#cuR00gW@vkfU=&Eyjqz_kdum+fEr`a)#XtI(8Ub8s z=ZsNBOP%3u(Ld8!arwU{O7<^eA%a$(vjHr%#PaChozeX}YCrD~!OZagLn!_}J3I~N zIVHXbw`vU7a6n|-pI_#WM0a-ZoDEC zvFk)w;dx9oOKPdlV7^KWd<+R{ zHlYI1<$UOP=5ENsjm-CR3y1Kp3cdsGl+G!Be4g`tP%QS1p@fQ);tBb{ct| zRJTxNmjc~E0jqWN2X_PU{MOu=&r442RlGwSEgym*MvZmrU}bcq}D?jFcd%* zP54^UxyVFuF_;L<$+9@!W_K}>wH_7DN%zay0C9P0pg|E59eDtqGDg?O?QWwSGF}~V zq?mEUb8zp)mvkGdy-EXhJOEQfq>8wlrD~-CG`(R<2*^%OfkW4V-rQ?z zJ=UK^Z|>40=6;vQ1Pp5>tl6Ma;HX&MSBU&7&`Gf~>uFt75EG)s-hx$YyIy1M z&vnKdC_}f}QdUY z8oJ2-SL*F?v^&c9L%l&!Rct7$me5EgrZ~AVZQo?2Wr&PtBLAUQ{!X`veES~pj^cp7 zVddLNT5{zH!Unj~s=>9KL@Q5{B;NQkK>5;T=TCuKL~+P@(6c(NS1ep@{G+lwV9yiO zq;7CYktg{B)wmO@GYxq^67Dd}0n*$Mo3Sl`&OQh7%bcL(}#3|N4+&6%7 zw&Eb-K)ea07T0ACoi&@MQSV?HsinhCHk&)8iOz?K^s3(Lfyl>8RoG93lmz7glreW8 zyRb*{^Cp!N7P~sTRe{%XV zq@$B!1rJsRDedQ@Oy-1pi3A6CD*DuWb|B7(p*Z{r$PY2c^w>}pTwwxVNb|qmC;1mrn>1vugT^MwsLB9^pjhOGb@8~6 z*L%#s_J8r#5|wpha4ZUDOT6jtxwvBvTBHeES)tMxHJ3h{_h~HXwvHAsg78kiK~fW* zJz>~6#^ls8%~BqqDHctDgNgOk&`Si=5BX&d@m?8$=>uTW^O!DT2?U)ikPZQl*}gyX z_3edoSPQZoJb$WSQ*6NtW%}^+_h0s;lq+ zDK}gb-BYkcT`4RaeXgV>IYVv8B8E>rHgH<*7vgE%kW!FuMgd_ce;~k@ox!Fc8-QBz zbQr}5%Zq8r6IUeWkyx`E=$kr^{IWJAV0GhsAQ{;VcDjr?EQ!S>0^P&-UCiz&ya`X_ zVC21gEYvsXD(A2#tA554&G6@ktb_AM#{vFM%oR(L8dM^vUs+W@Z-T{za1sqk*}KI8 z`#j%94z8=l^&y#qqPrf7_jCKd4jWCQHg z5u5h_Ll83i$9*AsyVO-i++G-#+fr}-=y4y^vIono1cC_>6c$7eGfNZ57Vd{+bzc`> zLG&15S`NXqb=ZtV6hqoEXBlI@*74A+iipIxWf;D6Db@GkdrX3^?+1-M~> zAt8f=)2ik0k0A`C`Ff8?y@6pRMFG4_aUh3lSHD0(y!tJC1 zF>LPhA+~h~m22Y8j49-1t>tk89{o6&?yrfUz6!`u=+#CkS>)zINCD@y+%$yI*>0c1 z|L|-($@HikRUXnMPrKDN1b9TyR$fK3~-pKJ@n=X;2dshT}5v#EJJJ@2{`*bNtNLgLMItQF=@2_obA~=#sEkr zU?>h|!Hnw86KaEwX$lp1xY*w&MFd*eGpSrrX(j>ZVE+>m5QxpTsaQovks2Wh#QTB@ z*eNGrg)D$+??x9OqCb$u0aDNB-n+s{u|>IZ|24-P%REDMey!x$jDb%Mes>DzNWbC@ zy9+kAmk3b_Y|2DKT7vn_)~pnKvawupvK5-eR+`&I56Pb+(ch38)r@t6aG!-)Z$SdF z6!KLiG1%KboAyDr^Mz~p<%V}?#Gw&U+gX2pJ3)L>5W1oM&Lm5f7|6H19+LK10fNfK4;V`9!j>Ir%{oXdTKX#wP{6RzYEpBU z1Zm^NeC=n|EBVMu&;*13NmdQ~7%`0rRyZygMG2Se05j<{EBOgX>@fTRS=NHD7K9_2 zy!k`7#9tQhlS!C#=x^GSad;g7w>_gh&P)~@X90zO3pgE;_V&^Jg?_6RlAOZ301bfK zHY9x?>-RDd$RP#5&zWn)Ns!?T#BKCb>HiWFgrIfq6pr*lJzyb#o{hu9)HLo{ZRsT@ z_)&*##27BB{|04*_oN!>h^K>5L0z`=FK_(a#RR75fo-KM*}*V&Kezyw6O4AxR*rox|WHWF(DqQ?rl7pQ$gQ4&Cl_|w6N+T)erpn~CpR)6z_4G+{p+psh}Y!&4Ym zNBR3nk-V&vQ-HA~Ng-cJ^Ce_gB1FN1zIngqLK`-X_lzx@pxeCrnjc0B05)w(40;v3 zM-nPX9$D=mNxYm2ZsLEGk?UbaReS8@wlF0s~e9=gza#e9>EY8}=pXI-KR%n2y z4WukO|9$3j(oEb?kf58`O7qj!1VZl2Jy$6o5px6+uyM+g)D3fmy zmwf1ZdX|%cdGv0k_gn;0b*&!CqL3l%I=nX&8=$pee&U+*ne`LyW+3bw!2-RR?%_xGlrrz$ z7%sFfu5SET0h7|6(U}PHovntM`-&8`1sfX>_*y|jp%$5T_I{Cw4lG-6)WfB-POjm& zB>#w0OAwh6Qp82#>T|@wJSFdB*ACH_L`rjn9(e%{IjK^5eao3~NiM7Lu7II&V)=0Y zj-eNuK1^xL`#+%MN<1ua z&RO=`EQf8^Bj1@9=Exft?Uf>0${#{8oVfo_eYJ)h1_Q`0b&oE=H*cL9X~Nhq1Ey=o&|6nDH^wODYN_P27wfbpm+$cwL?EiI0o znvxxHy^$8DZr5?Ed)JlydR|#_9>fFGZyB^945kTSy0C$0`f|B=Ya>h^iZ_MuM)n-; zUUC540%(&9fw9m;MxkjNVnz7#^|#^zYg>#_Bt7X|rx@bHLC-VK_qWMaHNl_32@1UR0 z;m=*yay}bT)ANlRe}0Mj2EQiMXzkCBD_|P=8=WUVokv5r?E~+k2tI9agLb;v}@ zbRZ4KFJMV-!^u2yEHb2;bP?())@lp)X$?W!J};z;N_+F4ukhSzqCW+6xhCpq)?n#> zHi&PjGCGc2P7tP{Ggd4*PXO22D)uk@NG?~CEAn2=rg@nT5n@*YiP8?PMf7-5J zt3Pc+v zg;d73%u^@-$e!pf6MJ(&1Nvub7no{+sa5wWjNG*4SxKo3Ub8@WX~g>Ypr z?3R(z+I#XL+lDgUMWoCw_zP`WVTc?w5>~HIy!ajbWM94-R;se>?6EDj#>hL0<&K;# za$k~F2SMvszXmMb$Sp{v%78K@0EC#Y`DNz(*vFy8<+%^(O$rl)PX2iot=cn+WWE`o z^+T`zZBGSgvNd~(Id{r^u;B;)Y6<#;6uajYyotbl7G4`4DUEn%Xfg~)y6Mo=w!}HI zO$KmAL78VEe+J7pO@-Lr$JQt4U*nn8P%V5nI)e0RJ7qzGbPLCtLL!`>%shT18TYJ* zM>pMoM_=R)6BNtc`-f03`kaBlED3SASa}}ME8?S+pfT(P>3cK69wu9%yhq}~%W+EE z%P3#~H_B&S2-~P#uy%imCm_&>gYqF#J&CvXV%|`yrMCCq5S91!1Grwi>*j}SXJ)8g zA#aa}_56)vNnQ#A?*quN>eTCI?Gj*jie5jA6S(d@l@*d$N3k^@R}D5h&e0A93GL6 z`z(+*f@L~KayPJXtJKsYffNyJjA_V>Qvzeu`0Y10B8TLPm@ecY`SaFb?*O4_C1k13JTq|aQe;DDCVOgdpF9TnEg>dcwdn$~e_ zPoon>ibpTrRxrquo8-51#=ZwYQFPKnUD0^}IR}%+ytpOrgD)!$IULC<^=N6Md|rB~ zEj)R3{J^Jb3_qZtqyD&Nc$|Xq7aDN@uHuh5LrbcdgSLo4rzlQE5rdZ?-goJZHXK=m zEh#e|Sc`q{WStYGF>t$vLW7N^2hv}Rwyfp7P|MUC%mT`l4ySeWw3t@{0?mYI{xh!| zLw#Di$u|UGNkQ=e;GowZoH+86YysN4F?8!6A6^nao&a>w01f=vXGtcel~A*Hv=%s6 z1D7#?xnBL}*|*xnXTo=S*peL2>-;?Ob2iUv|2U(PF{%6lb!U($W56X*j7v%77au_S zZD2Ca$=3v8JNRiW&3ZrQ3~2lrRF3|^W}^x7up_m6@QJ(a@wIwOtOwhzn8;}XOO9bi zT!7#DE%1R6N*Myt$dcP;dCs_~qN#L);RTZILi3pj_&F5bDHHK7|MxVQo@V+#iQ163 zC;TE`bkH|;#HB%@A53$A5v|dQ40};OoODPowWjBiliE}&y*Z0ICh_;T?R=2;A+m+B z1z-oVe+KwR-Q*&gvO>EnYVxwTM^~n?S|Wzi$TL(~KxMk~3qBG|$C1O1yW5VqOy+}s z!j5s0yf)Wz`56q)lS>bp6r7q;E!mubN|>$<+Igu`J!Ub#LSZy zy*1*c?e*VLI*}9JfE-2drFH3knERVKAjj>-tW(tuYg zmV1l|unfJZ0%j4No=2kK>^+rxn)_=#>IFHe4SOBnR2Z9Hmz{o7>Z>Mh=-9SZ-A!Xe z2qtiA7$NL@Uu4zt*L$4bjFisClA>^A`x7N4*McS$3jeXl_O7r(K9lYdILc|jQ4&A6 z9CEq)_%|gMe8#`(z0QiYZL_-O{vb3z#J5H2l?9j^UN6o(z&S?ILa4lV9-Lc-^Zrl& z4RIj;S%?EYqKYUSf<6369j1c~#lxNC>*M>Gv?BYVLVvZCj}BmSn%*er?i2NE{m~h7sUGfiwSw*rMYz{>VH3RJv^YyJNnA&BVB2(pc51|3#!_nc=*#SOQ9^o`1OpDWLjQnob3;+8c?AIyj|P- zBy3lr#yI5zWy)@9LMv{a~hVs`grcejy~+IgA!^P>K~hunaUP>CP_IcS2t{o6|i z^n#W84z3i6QYkNnW8jaw>@;RdMjgQZnqft1oM=#DW8Xrd#V$C$YS41~BXt^~KL2*6 ziwZDl2*4APxmy#lY`G0}&VKGjYq9&%$*xOS0lU_;ohaI1+R9ji`_PM4)e<7ZyLXV` zvx}XsV>!O`K4*@H)rGVhNUW8}RrqnyC|CpcY@zO8 zr|&R;nl^=eOQ(3J+9cO1`Kl3a=y;uzUP#p?r^{n~9Z!r>m^Bc*J+BhDXrqj9=py!> zJ4{G&{Mz_N0(>2bPeEje z1yclh9vlYDUBwR`EcPN8+_qM1R1Sh}1y!bk_^JdvPBI(Kjr+xZ%f% z0q7c92>mr8wF#rbDMo-fo%!#ESqc+=uM!}tSn42q)pqy>BbbMYf$i7zR_6Asa6tLb z#9VZN>tNe?^dW^P|DRrqrXEyI@94+usK3)PJ`~nHU_jcZ4X#@n^pGG1UWaI$=y1nA z|K?%-#(^(Bo#cQxgdC&}$vGZw{eIPA5v>c_5Ozy`l=JCv3hTQ>ylwyO%;*y0QF^Z+ zTS9=|NAib;!R5<7{SPz*YD`S4VE^(nFHR(mqo1RrQ11seuMV6E zp6Oj#DS_T~7;hwM4e=;sx&F%W-9%9;q_B=Y_0cQ#$Q49t$1z9Jp73D1%%g6v{j0ac zLb&OHgjwbEJKbzX_44YcEKAPnIGFvx3TAe<&J^p)46j~2p$l->03&T;B=7m{VYf-g zByRRub?6t!aNDY|H%I=SDwPVEFEM6QJt)3gAIZORK~_wYQ4DpKdTY{07$l?frlQJ) zi2Fb-npnqB*FJ_EXgr?}W^X%5nv@2q%aAfZl-IqQXq> z##UbUUAIKH>!b@x6tAe)a<3Vj8IQed)dbFv*eLJRFNf+G-&-yyx&5y76`UmQK>t$; zh?kr~#ruA4OwFnj9CGbqCFf+Pup{?iq9~y9S~3k0a)1{64kT{{=XRABkA@n(pe6Ty zMMHjyP|XByKOF7=0qmr;hTlR^5bRKqJ?`?A?I-Xx5P*&&IBT+X^OM7qn*Yf*?&3~_ z@RMH|kGs*5EFfuW4DhX@Hy`r#Mwc94nOKa_w*`^AljxB=MSIG&qt9 z1xGFo2t-_~$|mO7n9bL!4+IcKtr26z+I<&lh>J96u9*`qVogQ!UI%YjeyE!rvWqEG**I%-Q1kbIN>>t%4qu-?(xu%K7h!rP`uAOgKp!!Sf! zFKtI0j-=d=aHApK@#j_iM>FgCs~@O0?e6_OnMYEmSjZ91j-MK?wHu{y|3!}fh4*L3 z&su9*JSGiD2!ItQ<;-X2&s6n2OWZ|35RJ1^X?fFnMT!2<&^QSRX>GNcQ%;`pRl$?0ae21u zR2+AeUlQ%Q#B@rgo$1W6LS$Qpd+O2g_tgTjyU$mwAiwkE_M-e_jVI=ZqwYzWq5mT{ z#{Zr+Z;DqY#ET(geIXFoi(U~|lKh&f3Y(;7US3agDx(s8K#m(puO4#I0Ku#C{EYn& zPgqAHwiZLms_7_q#j?h+HYZdmJMSVH-|Mkoq+LLp!1V_LEhWMdntc2<^s*1~Z? zTv@20MgP$6}`Tp5E6Z4lCOx1spTJ5nRW-DFC@U*tWFYlq_+E`PkqX>RZZFBxJ`nIW_ww>u^X8 zuTmXrddH=52 zuyVNZb=^hUqq{1pE9muaI1P_qms=^{`Hq?dWs?KLD5 zT9UgL|5tqU1zyH(yD@h?Wv7;%ueXn)bv?kP4fNBhxOi)>{_UUTSAHL(& zuXQsmQgt;_r|pZuf(A#^)h5TpF7a*fk78ZZ&%TF15E$4u>MY*bRX&xbcm3J*GeE5+ zM#ggL7L_AWD#?uKKpzyZ=#WQU>pB<}c}a(FYf}7?ErYV-xhi&JH)3^(hFgP0RcQl8 z>{??)8EB~LEXF9>+mh&A)vk5Hph@IZ2yz1bd{vR%7G-z7z^d7wTnX~Le~w$OZ7XQm zxBb(aF*)_jr~&$7Id*?KGCaeV{OTXb9Q@*1A4cs1Z2_2i-zL##?qXtItedj!@DI|q z6GoBy>x;IkKKv*C5$+-KrH6?p)WJ0B1TO8w#l<4?Eax=B<%fyo*5YYjmj5QarfGsp z_dmIz$~k8BoTe$_p(t}D@h0=qI83M}kC!C}V_VKLal+P|9S1)2-GPVZ<9I#R1NM^% zP}q8H_T&&-8TOptW%UGj3aFnW9Dv=$g7m>qdNNoZ*#;OjLVlap(!F6c>%31kNSXB z6+QjaamI2IEKkXrs%W}8m`gQxsKVUEk^4ObL>22#K320HhEH7Ilh+}`oA^QG7@0quN;vCgVE)i zy%v{QopUfG#Lt|$``cY)_#!^uri>)lU%Mi3Z37AB5vsZ*n_^iGfg>EjJM13YLhUmf zJQ=PC*3)D&O>z7`LBt$4B81Z;mLU!uzSeE(Rvwq5o_k&pDE6cFG%sx9?25hG9mrhMV)trV>85Bd3o!+QnKyG+yJ^F3OrIa-rL*%`lw@{|B>y6&dX#gv8 zKJBx+65O1pI2&rcxW+sKSP9n~)AQhb?Sxx*@O$jl%`aA!d}8RY#AWDm4+~>{qZpuv z%fQ}#Z;5)99@pz7kzf2@DqAcTDXt=HC~u^&p|p~qM}zvg>TafoQ!hSakWkRy`VTVvDyHBTc2jHxOSlS+ zL`a%$|IoJuvn}y`1fDLUDsn??N0d_$1#K4Ng!+$@rhuBoxkyB5Iy7JCVo+1HHd{xJ zV!=TP#V3CAIwXH~#_XuTu#|7aM5sosxa3wy6r^VYVhXbD#f%wpCk-to_|4$r5bA?P z$X!SE#K;QO)bOjxaByM)`4Q$s;?+LR5r%_b>xhlmpW!GLY<8R1J-D1-!-vE4PKC)Y z9L(q%;6zEWprA*UI@D+Eyby3^d_Nkt6gOHB*YYr3+3Lmh9`2W0>FKlhJew2!ozq?d zzGL1<+r1nlAj&*D%ThET3fgvm`vfMA$V4Mz6m$OAH9LM!_k(SS0c{ZZVr2g7uGe>_ zGqs}00@YU{7l2@a__+P5tpk^NiGiBKa>_gVksZ>Woc+K?$SAj7v#kbt24q=}>>y;Q z_4?ZrRhIV+2&m$5+#I{xa#PWN7%}&QgPdf!`)upC9DVpN%(Sc|bz5*)u^FL{kGsS& zDj!$J5<_U|6MI#JO;XeFMms6VJFh{eWESpGYwp#IWHCm$WcXKtoeMX^6(&1EU$xm0 z(JXMUJab2tZNB2k*z13^*M!k8FEtX%^vd|8CjI`~R+D<=Fcc%RodBU4a~0V+$GPEo zaWPCG-}po{oc2L#`?m&JnH=xz^-1cK}-@UU>xZ{6MY z4=bl(GKgq^4C48p4BVF~vp)y3Zwz{D{NvAE6l^#2JA8tL=n1_RCX|G|oh91^GCGS$1y5c$kOR$Tl^kxf!Imw69xpxL{F{VWyD`UTrW!lmajRu z$%66e^|n_~`2YPnfOm1s^dSpt8`~NyK>5C#kGb zW$a+TQw+A;fUlkTSXN@Wg@0Ao56?^V7r|YBTQ_k>{B9s{_!Bz7^Z|d+mW?yg97|G< zZJv}qbA5OiwGXJwyV<4VY0AJ24ruGW;S0jK3NU$iyL6_c_c_D3pm{qJLK8=is}-xd)9Gj+hD24UNVWw{+)uMpUGpUknN?P9kJIJHT(=G^cE5nPe+{U~^?h-)#jLJ7n z**W}Z4Z7>Gy<=uTYHX}*$tjEP$g#9+J^cv#&|%3Mz1F;6WYBb@xzPOZ9J0m7pC_k| z<8``6X4q}(PHUuxnTGvky6o|dUfUt^GJQuR&(nGxN5o}S6x<>anjP0Uo=r=je+vBY*=h3y`}Q|m#|V)7(d-9q z$il1V{{G2D@*@arj-24HC~$d>6@=1q9VDj~4JvQ>aJx6juaL?n@#3eMy~mx}KuMcaB@?p>S8T2iDa0~gIeR%O#^fX}52d;3}I30~aA zEv2fPSrXTUw zPXl63{a6u>lG0XN?+@%@AX(0p3DsX;EL{z^p+Nvo;ZX>(?Fp0dv%Y#S^NkM@Fy}mf zT}KroXb)b`MYV;2+&Q7%(~c{lqcft`^I{t^=iKdf#Ob19k1OFvOwdxijxzVh3wU*~ zJ$%Gc@A8-#|Mc)n)Bkkx33T=_*n6uA&i{L6d0J;8t*SBtE~!1;;!&_^*wL(iS^GE( zJo`AS{+fa}4#fm;KE4XulImqhA0RcW=>a;E*Mr0o`d}%S&e`>L!h{k#CMx}G*IAuXm4xx-dI96R z84OtM9Bn|`-jg|eDc|`itdH^$0$dMZ#}dQ@S#%)Z>1_TD!&cOQj_)OcdEz-*xep~| z#&&E|DDRDsnSZ2cn|- zd*8MJ$rxv9^htNVzi3S&I_|FF?ch^qt#40{`Iu@kwa+@5;qCG)A45*|^`%BR0))zE z`6;v{wd?tzj-BA+RxO`rJ(=%KqZe_Ao{@nc{@8!y$vV*SZ{hQm2$(LDO3iaz;bi-1 zhEOqldYY*N^tFVO*{GAr(;TdipIkZ`Ot`O)8|hxIhL+CA{C#Na+i6s|DMu83Q=LgSg=ODOnH|_*ySS z`l=r65>`(KVvk9c`y2Drw&Ay{5CD|p88f+_APmq=b3Qube;fK1yB|9=A&>0rf`5Q4 zwbI>#7d!P?pEO8N@%xGN5;GVyugSdEr>1Fz7w8|!VGhHesMW9_!0MQCPzcf1p_e35QO1)v84rEYHJ_@6f@RtWo_2I+m2q(HmeT+ z%)o$$yer6{?a>qzjF&-ls+xZW>phM#!5F;PAVl=3iY2EU1l@yfq&>_EY$J$W1&Z&$ zt$&xwg5z;w=R-jB@Vqq!v{1MC?axjK?6`~W3Uoccc?m7ng5(#^GjR}=Ij1bRq5-%C z$9{dw66OA#2JoN}jO^@?-Nds8By8PVb5b6=HLp!LQed{dMTp&=oAS zkdrX~OQkV4+!HE~<0r<+{m+wNAZ_UT*p^>eEy&+{U;1B;G~t8f1(|y>nNkVgEsS`j zJf>ry5g{07Bap7d4=Ke28_!8szWsJd@GmxBBvWT`}lDtWN^^q(C-E?qL8j+F~NtuG@{uag1k=KZF* zKN^T(I961vcP6OLotK0?g3RfzdQNTJQPN%ljwQ_i!&6LQU9t3MOd* zARz61DR`|5T*<)Xhn80dr%8Dl5LlaUdx?M;lC~*4QT^oLtZlHiWv`6LyrOrWatn-t zI9e`$L9m-D6*m20R`!qj*ZQbpMs`~JC^l$>a)GYr`LYW1AgU9eE?eDwi9?N6q6}}I z6pyb*&*XQ>6+qHixzAyn;{4OoVWzte{^n(GcIILFr*UB#8We1=oy-6i85jf!)&L4J zW+%0`fl*$Q#Y6-M8u$-75-<{xE?<_uC%={STDGZ5AG%Ckrh0XJU{|H!R)~6h<=jW+ zcKbKUeE2TIS)L%OSmAuUAb7NJQNMtTth--ub*B&mqWA4pStv|Pi<|{aa?H9e{d}RO z?)m;BXGtyTf!OuC%o}g42Xx5J;$dL%5|L4Ndol!){-k%{Di$GWIOu!ZnY&(WR8%OP zj7eASIjx9F0Ig%)=&P-D4PF`I-zge8V95DaX5?<1yV5cgC`m8)_M8hYYgFeOH`MwY z8smP5WG+tKw#!@xr;lK>xJn8!FV@?zaO324Z8dQuFJue9yQ5A`AC(GGY%~NxK3cC# z)uz5$`Ro}!F7_K%or)EJb!hyWj`(c7lh#LX(7lqEO4==&+XJZDZ7bP~WoeKL~5BEiCYzZ%+yuSE0Zc$7N$XX`rz@@|r z`M4Z!C}Pm&;Q5;MJ)Umej=zlGVbaSM9K3Ilf0ev*@r2-U`$Zemq$|Z18N>^rA4hVt zKe}s+Zt4FK3*pws98kNYD=U<{r>F`cejqZ)dkyjGLP;YLHBJl&U z6F6l_Xjf+bFaCCocM-9+$LDh^e+)aN@Syo;+QdbJmCnmgp894ZGzR@wyIv-?f3!F$=RK1+L z<{xT{iHETNVVqd2*Zv^dhOArE1GCfzU z3kG}u6OGe{pM$dBDmCiG%9vcLEv)--{dpGgE{SC$3r^^FiX-1LNej?f%@XtMeFXnQ zUi~?3Gy{dzU&M0QIZ;^sv4BO%=>9EATc}koMx?a1JUMFANXJn{W3A2`4Z)t$wcyG3 z!DIgh!;zIrrt@cbS}APY&>}-3DQ0i_+Bub5aNd6nNM@w(O0_y?VjGdwa~VWzsaH@D z?zF3;Z99o}SZe-a_xFu*5el|aK!R%y`0kRyCS)hwuXYOdK2ZpGuf8eJ2=uENv|F~~1iv}CXpO-+9$wjo1Pt~8 zS)p(4_;wT)D|}d2=&Nma$^0oV7x!}lEz9c0`1!oAXZ>sfpTSxxo1x^a7{P-C*fu45 zWVSt(8^YA%LdxNtFz2s)4qYEV58kQiX2nbm-QDOhkCEX>S4HJW}p^ogsKP=dTGB~c*))p9yJ?=O*D=>5RqB#mHjAgL;eXq!09jt z_LlH-Vni_q?4d73Lti6A1h;MC548h1OQI7R_8|@P4;H6AO2_wwu(_il?YkSVz6K-E zD8f6Zd=#vh{o8rZ#Ds69{h6u;q7u%OQ~Dlnf|-pT7bOT*FE4q97ku)oED~ zdx8d5@*s<3dHQf<>qus)!jv7tHC~}i3|~_i$O!VQ8!lGnmX%2HCLJE|N>L}ge5>w; z^8Nz`dM~$h2=9f*TUs?ebu{f?Bf+wzrFgD5$3xRU0_&15Gi(x^X1azNQxtBNw9gLbz{#>#7^#jI*M z(|G#vm{f!xGE;f{A%#u$eE0EVes-7j(A*Gc${taQ9EKiUy$?g(`G7wrnRX^8uC%$* z9&wSgSSRHW>H5Qy1rbs(*-k7Z_Nw2#zT zTkXiBKfz&81yZQL|!9~(yH0N<2Akco)-y>v)pNHK9B z`v{>@XT!8uEDdbq1}t-UfLoM!f+BX0X>$NOW7a4Bc-11Ydbu2!H!u|V*s)++#7{PW z@2lkefDUS%2?b2$XnU+sBN6Vu1K9qDd$`j5UrpJjtP*#fuqjg2`;sK!OPT_PIm%w| zvX2fuDPtp4W7D|Hfrs2E<3D3#R*A{A61n%2!HWp`eGhVa5P4_yJy6c}=~du$z<5Zv z(DD8y(bVGVsh5l?Or#=vj-}5$qA-t}O&{aO2UjQGOiy+)xvoi4p=wIhBv&tuU)0xD z_TjP0664oy{VXDEVk-^Kz`u<;Ei>5c1^l@#TYP3KYYrm79rdIed(poP=3OL8ucnZZDNw6Smr4!0pjtdnQntH zL)vJ-^E^L+*d0O zbEosYSETw&He4V3mVSWkgLvX9f@N;|Th;yw*7FWOeLfnKbo~7q6V&=l-(gTmR6jK* zmKMioFvIm#QKATPr>aNuz_E{_iz^GL6$eSjmz;)+pS_3qCq=;Ikvv3bz?p#ff_{L5 zUYqHTf!?p)I|rk4+*et{nG-#jSqep&*UMr;my1>p98HnNwjb#wtGPbv5Wd(*I^%z`=dR{o+j0rFe#1oL6Q`yPP zqHrb|TEgUX@Sy~PuJ_$wudGxXQnMZFvKY(N!)`UQQyZDXv8ckh@|}nk#~Z0=;tdL4 z6ycZ9)%7D${M?(*g{BirKL+DatahMdtWxcOjAIcHNBjysn87hiZBwJ}CsoKjA}v(9 zSs$OaAM*6^t?gL8rB9Pk>ztX2Fs(RVkaO~-2fe)M=80+VUX-{yJ}KKoY&Tz$ymofL zFpJ1%s|3oS1n-FtD*3IhH`8vAg5}^64U6X`tC4>0DpC*8Q&A=*?oVATHw`?ESXK=H@sG1T`e4Z78p<&PMD~91H|(GFz@kW^y{akD5o}-)`JZ_ieX8(oBh5xnBAE~>u z(Q#2E|8uXlMVe{)u8|!FaG=sL)1vri>cwT;$DB-c2LH2=G( zwCn>&Q8)CxyYFvmzt2Teis6yEa@OA8G=yyy(eT+ALXu3m=+{$O>>w^L1jzYpW*$+9 zLp$_?4H}K^S|)R!OSI~hP^KJTr5Pigu)eD2)3L5h@rWYnAjmz{h*%qeEOf3E2ept{ zf>y3uSB*|OK|wuJLH-{a4lVQ(Q?y5p&J>F83y!zz@S{cL%=p45@$~h~Qe5bWU%&LB zSxC92GTGHgsAU0dzJ?oLSo=~n4(L|QmF~(f07!OP3QTlyLhp?m&{sA_~VtAkZF=}mFt_qb*fB@&f$COWK0v7GLosgPPzB{-sdD* zSG^W`i!31W&3gCWIQQ^F_Kl3M_kTO!#+pXGveY4t9btbxU7f_Q7-H9I*Kr*$+Q>B7 zn7RIaOsWt?#pkwf1ia+w9O+hjdF7-f<`AIB@{?oo>KS4jExx5`5JT|^U=4w&8t(5J zi{nZm)QK=FSGF@0<{>)wY}+Dp2zm0-fr^@L+g#qsRyj+xI0cD)-+(&Yx)ArbYJ)T7 zI~qNglJE{|xCi9%tYaE!aFzEE=R?JAW9qCGzZ!B18N)bb&CBeAC$4&Iexz5jL4p zNL=q{>LrOnARlsMHxWuGejP7xI z?wguUiuhLCgQNAdPyN#?sZN}n?4?)ZYWag|I&L4TwY4wtDnE`mv06WHWd8OLKefJR zE3Jf~uq{qXreTVt*nk5+PjX#!A(G?J%jql^59kH}8F_Y&ZmCWhR*HVHOUa8a*<)Yj5I!5NS0o z>v&;qdAp9UH7vAxKLu6L2LDkq%8K_nFJYA z^L9&GU&e9BK2CCAu2Z#d-xUI3tM=n-o}R0mo#a#iiRLnB*Mo6$-hj=qmXLs=K&;$8s!Wj!VVKv)e6rX9P!O7-Osd zG>H?%zWJ)X!*$+KTmJ_BqPoIua+2E!Wwo7%y6?sjJl0>+uImus+!-Js@Ici7cNctR zFh8f6slCpR!s_`N{5^ui#L^JcMVIp7O)T=Lr?!QdbZgYKWWRqEkl}v)ds?Cx6)TF? zp{}B!!Pp(%>op6spClSe;tDG6_lO?i=8IvC9pV;RuQ90o#&Dvdv zUELrO8D~z;Z%@npy1O>A>H+8J2QczHSZt7DR2-sbE9ze+B!6T&iIXF6W06tHP%M4GUW~0}m5;TZ{ND}o zr%9Km`B{Z*xnlIH%q%>2@}Ji{{)U4h(5m;1#dC-&dczQwY8P`=hG}<}Z79h60Qg0q zQm}YKv38lY-?y)aTaq*D=PVJq-XjAlM4@BnKAtE7r{0^^#Eoc0AtVeH{DN~7fZm_w)AKI7L)^IZnT@R(uzNuNrIU_zuiOlo zmP}EF{9XVkU^aS5It+y#I0u|n%{2SyEgraz#$2^p@hM$dJX0xUT>k!_G;J(i7{S(3MXK3{cbP>8?*>-rl9XZ$uT}E-w9R`3c8x`058rYs-^L<5KII(d#$FvGZKf z>lL|}X{diHYkmYOY>&%UEx$cKoI`It&A(ImUH*gti|U&Mje$sOT9)_{8NL^e!k(LN zmeit(gILV~2I?0R&gW^Ru|AjV^Q;x2tluE7ky=V${9>$S^UGLsz-yn{iy<*$!3&oV zSEW*v4j_w7zP_GWpk9)G12fG@L~`n#NFXYG`SqM@Uh4uc^B4w0-L$H$JTuls6Ps8}0S49}iPHWA$@MQd#<;I*!PHQaP>+Mz<@ zT3iwng>|**zcS=qNPOZR_7vzCicMb@wC`+06h?VJTwyG&ZG0^ihJU$m8nbZborQ4G zy+O=iK0Jqk&eF0(jrm5$E6e5`$+tFS4=4ynmse+YWrcO;l7nJ2)g0XUj+jqsQbs?pfWqKB-Gm9H zW#pO6=-j7*m;rfX2k9e*(?12E-9LZ+$a;2uc?erYlt)M@(sp+!WINI>=Xl|)=$vO# z19}*o5v@_f+oBU`obnV2JMnrmb^{x#xJNQg`a6hX2jSST+>h{@rj;UzE4c{I@!R0x{kFk!ms}4TnW48236-^ zY3Vm5praj-TqDbM-#ena*=|O-Y1q_^wmJ*9I+iLJt3Q*3EL@+}`6Wg_ZW-wRxZ*FyMcZK`08%1P4~LEQ zv2szArlqyUj%$Z0I@3-%c zf51A{E9uU?LK=bI(e;lmXJUU?k^GfUEbi5>8y507*ikGJRRrI8_oP|(%h$cAeH*BX z@0hEQgqh|w?H2#2KO*v!B`%t_6>rk&y98t@9^mWM6t9kh<>odD;t3!5q$_)m#hb)*}Pa5ME=5{Rb9hma0YE~1&M}eaS5hD2p%oF zhTu8gdOX!L^dYh;bh3wuo%3RT=?}knpd*G&L6&XYj!8+XT92SSB*rh>ClC0v{MpS0 zq!Ola!jv7{KZ?`27mV+%(4aW2qcZF~zcKpB2j@nSDAH%o;O?6bR2oDe>)ddtV`C|P zKcCW2J2>|j$%;t6i>IOD4Ny3)_s01i=pcgaoh09RggjP_1P>X7Gn$s#1mg|pZh2!-lN#KzVHauHiT9PtP;-jdWx`X_& zpI%$yCOB@3Y24EJD;GgJF`DmN%OmCd8K0E&3kVL+@K)VokN~SLF@3!s-QqCWr-16M z)!!bsfgYt#eweuO_p>e3FaR@rUfU9GA#cC#tm!WPvFY>*go=#?X*v3i92Wc;+#U%&XHy29c$Sf^F^DHT=bR{y0LgIwY)N_ zhv}FS|0_PPSR;R^;F;1Z0h~#sK(we3I&%6@HRnWMHD-nR3=Q520yWGd@xu-KX zR5eQq_*bvP;;{ZlxW%(!z(l~aRXX=)-T%zoj^uq0(U|s@jc-$LqSBdMV)Ha}ly+|F z)!E^{b?&RiQy&;7sQpJ)-YbrwbFQ~OWqu!SXCf_YZQUfY$(@V>ulVsZ-!bW<^~+;M z?f1AIJwh({eZd{RRo!Jb>YmEcKwzp54+Y1+Yi>Svj87wgW)9W4Xag1@TV$Iqk5LyG zkWarWdYD9;VP*9b+WLHr)fF6Di66E}PDl-WzV=ClR~@SL60JZ3x`Zx!mcnT*S>IA{EMm+br@yEAdW3d5$8B_%9tP-PVWU0(7ecZ>*Q`@xcc3- zNZ&Glw2<~hA&&I*qsNg$dHuRT=!mbWyv=E-qeskAMjBX(8|xfK-^vdkkgq5Y?uu%P zZMuKoUECQZutC9^2eZV+W_GuF2J1G0!9CI8i);E5sCOP(Oz;{V?sPNdv0b~iusz~~ z4uWG9-%By^lrBJ;*PneKuG-AO#ZQut745JRdNInogV#VDO!C5qwO~bAD2bB)X%H(* zIngK2EAm;nLqKWm&1@}D1}O^WdGyRR2UB|Omk)-}HJ39U8kT-WmFATnDYAU|oV`O3 z;-P_Tkk!@Qq|B0zj@FK@lP^qw3Oysa-FGT9YG5e<9t4;om^D%17FA}xYl%Q-6AC=$1N7aJKNuB} zUp*eSbib`+{u39Jio;Ah{d+X@n%N&oV5$Sp9^V?So7Au}%d~&$8-}~)5|j7#nBlkc zX~pa$$2rVF$Q^{tJB`NiaY9F15)SE87_hz2u*?&C5^x(51^k3f4Lq5MW3_@E{g5BY(UkKIx6mi%D_MYfj=&<&_{%;RL(rL`P-6aY zlza%#&WsV+B;7aY!W3z4^UryAxn1=q#dQnvvN`9njF)pr)LswTO`JMTb!D4awgt`?Zo*wc~Z{B zU8enu3{WqA2LTMAnH_A4TmyI`briZda#}Rtssiu{<`E0u4JIUE4oZR87(df0Kz}p$ zka3(-Ip_@e8Z+Z2DalM6JeTU*E!(!pQYZ?qOKupCA8WK8b$xuMAtC z`AqQQX<~n@HH9^Yr069iKR-{hlKo0O`V@f!in}U&Y@ggJshnCUE4) zxrap_%_Op6IDGfA^@ATu$nMd;0yJ)B%V&Cl4KWRsBghwyAN~a$eP>B2H_^h{t#_eW z-;S&8`=Of>$Gy4vGc(x{jSqa=9`DgO2SMG`LV+@b0X)m|cW;d!2;ms;6hc6AkBaEXQK8^l8dA_AL zVnRBybY})iL`yc2muT{w%Km8nH{PNo;JEnapN0j0<}2I4*h8n#c%Lqz?UT0N4+<%q zD&Ey7a^GdwGj5(HM9QMB9aoGpE18Oiong8u75-Qi@Un`m*18XTP0q>vQUw|a9Q+7k z&UVVrj;6XkKJ&Q_#s1Pwb_VT+*b8|}6Wr>qIE2;*QkEmiwk@WniH=RxXT)~(40={U ziTIBos$P!2Iap}v`!0hX+rgpQdL?O6`=+AbRn=&Vtm>&!h-|YLjbF$7=K>RSH=5## zUCcho^j9@BfK8#cbeDB?Xpj{v^xa1G|qy+PfJWX?&L|@1rgr88vwb6gOswBB6mrv)=BKe+ZTnmrgJ`;=~@n_1JOqI9x_)8TKhlg4Z0A0Ya&~=>d22gglE@3)2@&m(Az%+^KWlM=O2%f|sb?sGb=`x+CWvLpk3;FX*aZpnMC3^4GtF zdTW84Z#P37e3545VjT4$&OR>nPDhrYTOdxHH5?&uXgz!O0doAP;o) znD-gcy!-gh?o%9HNS7ggO;4VLFtu2pH5>lNRASJm%N^Ni9bDRfOCjx-pReSmjSq1a z>FDRlvV3u$TK6<^FTVAIxMrB{Ds3)bXuDP&VCM`y-~~d2zob+;QYv|nb~If`z(UsO z8*51UP&taiqu(RjSSG?h2`!GA49VsDhNeY$?vp=|r))nrSm1`+)%;ZXOYC+gm6QBT z``^7KfLZ+ZT|%EzaYNus(;pU30&PXs^j3$G4{Xh7Iu)O4^<9lhQ3zLo<6f(CG4(}n zjYsPEud%o7jv_3)XF^M!?0_S(rSIP#qL`w$|1~BZSvjjUwDtJeqpCMS7e z*Y&!L$knnEGP38!)K!?zR;pMWQk?5LgPQjF)<|m0v~?Fy>fljhrm%l_UoeN*4gDLAxd-Oiser|QL?GoV$wBLySv~t83(4Agdp;NF#qPU0 z0yrJ&RZBhGvT|1bwkNOufCIu>ep_vK=Ou~q{Cxk)Hi_lMIRk{zDUnrn%~y4lfJM~q zb|>QX-0uMPKh4w~-(_U0HK;m;m4k;0=kmdmQF?mUqD@}kSSo_%P+CY!sdAO16*|*| z-~f=j5Up-|FLt@$J+ZqsMuKvjL?WA0Yb|BM3!{jEZMh|TX8%)Ul4SnJNzUXke! z%%i?Z;YSnNQ4R%F9zBnaTP(|GkQ>QR8=Y_OSAieF|KGeIy|aLK8S4D61Q@UYkM?l# zDb}VGxQ#cvVf`np#SB=qH*rtbQ-$Vn?_ zdFrC&Mgq*g9$gW#Kh#b&vEw}4edL)H<{=TzxxZ?Cdki`_&HfRo`20!HF7y7F;0nw@YNKXX#0kc%B}A2m3*u=&g=# zq?EL1@yh7%iZ>A}^ZwG+^QMIdl$j*Oqestw#k|n?_eG6_@@dI!9+#Ye zLV{ega>`P!Itq^Ck|v)7jPSqx$Dh35%6#uTMRbyT5Y@PH8cHwS%;-7mWGpriIIU8O zu$B2oJ!Y@oJ+e^4O7!@j?Kq-YKT?@>*I{*m2)-|elVc=nNJ(bkY@App(m2GW!s*uK zn}CIr8ke<6Npd{47x>TAX@!diX~?ratF2RGw0baruPs#UHm%q{{@qJo=b)llJbCVT z^R$^jTJe-f&2nWk&DWOe80TlUd&gKzIl}aTskfC|m`Fg$mvU#oNq)fl#j=GQtd7#V zbF4z`RiD+j)@taY9Q9?480HN_9BWKph41GkB`TO>n1>Q9*a_hN3RdZQ5}po^{Bz(?fsSn#VwZKpEP)RhbXhtD>C` z;Ctk$3{h+cc8v8u7ikaBOyEkO72JuPd&^Z=d+o2!R}jrY@A8iUq{3pDi564)j(>T+j~;Roo>R{jGjU**G1Zu1;OL z5^kJfmJq%JVx3mlf$lY)&lh?Wc0u{Jxoq7}4JFCo zA?-72d15)%AF0ZiWFqKH*+No*1=bG`2a->`2J_vJvATJJtwFgnkx@YJ zoSXR3bkw|Vu~t}p3Y&t#8AFa)P*;z&R+7p_0DXb|sxmmjG+9G`k#^4MihSY=9hWPt zUMl@)Vox4B#A|l%2+=s2A0EPw)m?s@CnenwU4vxrEv%vZ;I1Df=6mJ#>%gE-d|oAa zr%04yoBrY_4tcyXUlumPCF(oHCOi)yPZz`;7MAiHT6RCAuaAQaCE`j6%zswHYFf%= zV54Np=}w@gL0*87<_64__61GgL#rC;5K|&MDhL479KgWJ{5@O~MT4AU!yZ0kE^W$^ zdacc`gB4HK@0Z3ZEmV+TPl=yd$-6cf_#;ze+25NX_xy{~6_Tk(3BaQNLR%M*-6(G% zqE$xeiv5N34bajSnj2=q`(4{UjRxX0z1JOgX{)(-l4?{jM%i4clxVdJp-HmhcjOr@FfT!bLDqr#aA&nd4 zQ$Z_ygg}4t5V8BO7qkH-z5;~0@Wrc9qJ&&v#eSTPF)6EY*n(#m zqS5>DDMP!!tv44%bCF$h(PG=^>NN|5tIaJylPFB`m1`IP*p$HE5ndC;Xi z-?{HiffXi|tQQx;+m=DVzOIfOA+C#-NLT~}vcH$!qbk&wXf_pQyc-&fz5ka)QDIlY zd(EB1BDcP`!Ua9J@4(MODz1vnzQg|3VznEG^`F;bI|G~@9>Qm&55%@(k(P1^)V(qn z69TPjpJzi45$`q6>42y~t-D&eYcaX^TT)Qz1sn#Cbt*cQkWJe@m>-3~)-JqunYy#v zaw~nto^~&BV4#<)X7z@w<6;RbvncBnc82HxmoODs0M4&y#Y+k+%FS*AgZLN z8%$g+w;D^eKHI)0;n(g*xPPrWa3+*gwDS? ziMv0ue;=>^l01LZmSbEqF9&p*)#+b3n%hAhVlr;GAtOX6+jB({_Y`ZNIkAOD( z!5fNu5DO*xVn!G^!a2v-F7Z|0yNbCwq47WKt+Z-GU-g!RSMXjhXPQP6Nyu6CMn8ut zt!gdqr8T+v?>ZA2g#5gndbV=Dx(F`9CddJy{`jwBkY*GYqOc+UJy_a-$ZDWH;pjMm zN(yF|P5kwS>4@L`w}$S~UY6f{&MJBH8ZcHY95=D->dmYtJKWWBdk_e$u2*2($qUdY z5e|H9=}ql1%n#~;#yOgtpNAO0zzWda{l;)eApKYP}4|=?zd6Y3Kk+{d!bK1 z)an7q0f~#;mZ{^O0 zgZ1z_aF4&;&S>Szm*|S}J;UJ>&cC zPl`U_;MRQ$6@OSw79p;T7J;4;4{x$Jn_RLslR2`6RXg)&Xa7m+xu;t{Z9mR8za znEE3}8mwww!%JwTgAkg4(6kmO7n)4M=F1fXMaKV>t9a^Y1}yQOyA53PUKghWrtQax z5Jn3`ff~bWcXzk1rk_9o<}mU}PS$__2FyL1$OwaXgv#JD5rPmXs8vOm|L8k%$$QLAiUN{v%;CS1tgF3wR7OxaIsHwn%Ki_|K z)Rl1P7GID8+)osV95Y5~e-u~|dd?*t3D?cuOIB?FGaPW!?Z7m3KH7e`2-Bg>lh5d5 zR_rx5AId&|NfR7d^HbdB%aZ`Gt4@U>&G`J+Sz~5*BXr0Pp;9C4eHl?}ztMI6_0e$4 z*o|K=OLs=xZV882;G}l0K=If8(J$zF?HuLAJEBf_59-%zBUlowLk zCY#D|(ii>l!hd;obNB2$3E=VhCwgwS^I@Js0q2}dIrl1Xz-!oewq}0pyp~*wyQiZP z(cK_5`{~E4TR3cLDV;m>!G-P}F+|2+J=y>EXZacS1x7B`(Hnhiv`mfNom#q50^d2R zTSvu?rCo6W3T4p3@hoiaH1*h)6jL?`doU2ZSiFuVC@r3%qLqw>hQ0AI?DtTK&hFVDNNL+wF1XW$)L|*;Ve|LhtA*aGM8>n` zuDbMnkm-coF0W@x*HSI2HAe?+XGhMC`qYW_jcKu7<2A3S6Dsj#e zj;gY>3@=Q#u$!76Jy8&lHhULT>%E;rok12De?n$)D$s@)B)`xx3kJx46b#+YsTK-d zGKC)Fo#}-8R^&Y7pEcL85=Wy1o5S=!rlAVE&0;DLzdw)T{ctR6`(S-5?ZpHRP;fu( z1g#28Z>1UapV^!~9|gSB6<=*y>DVC^7q8g{-3g==8Tbv;2@p~D)Nc}yXtkCOmZ{i0 zI|4sgw@&G!d_~LfB&O83<%2zfK@dEc57Y%fzVyH7(n9%PcuEth{-s{cO?6E>WQvl4 zXw9q2lX)de&-=T7U!5EbUL8p*zgZ!5ciClDy7mQUOSDjwy+)$CFqB(XioGT)=hkDT z>1fe{b2ez0&d8uN0~bz}=f&hVUdc#+R$zp}{7e=+4WCDi&*<8Z53nF1UUuR9RO$&u zxk&R3GLt_)w*W&bAH)O94t0?B=f`Tic6(9={~5Of9NcX$kb4W+*4Ra4+-rx^d2(Om z#|s|d8{!8xw^6hK-{zQQM2hk zt!HWK7xH!v;=HZ+b-;@TX|*;u7^JmjOUunvza26a&|?5k1EePBf(&;yYRs z1uheet3!bwxQw+#G5~!sqIxc!KD{>J=@}=hj*fNbo~8WTiUj|P%%^>w>jEVbaHM>f z33)K$-apu0p@!!`i@4o3wkr&)xbxEc3XCr`(8Gb^u+k}77Qy;rWdim3Bud|Q7;R*J zEw;`g+Ebq$MCl}YK?CKkncKabp*driq)Nl}MnKN(?2d|u+Ot8k0~T8bNZ0&8{1-rW z0mg!?EVSE^@ypgFd5ha+Wm^dccy!>H!Na1Ds!iP&esG{nnF7lUfcweGtmA>~R{|%> zXU(nXI@+;s`g*-s&VMejM7PbRtZrQc=O|gEMLRSjP?v?nxS(P&;7JzV6~W4E46;0} zbkbRBXq+5oeU}+4^P$_h6^z4H`?lfA5_(K>GjLG z-(2|LdU<*6{=6$_-G$%maPd;ucBc)8`8qfETFHyU?!#a<)fi5N0%FyN#;lb1L|aUI z`1<5h(3lXBuEApW@PqJu)W(|PcDCZ~d2e}+j3fsvP(ZFMObhSh4um+xqw9 zbcWD;necPZ8H7r3jsdEU0{yC4FR=@yn880m4}r#AaH2gi;DMH9jC(aq*4Dufe_HY3 z^nAi4{>wHE*MNse1zNixsTrx$T3~-Hz2U*i> z_!3Oi(BT#v5U1FB%0_zM*)MX@)3ZjrJ*RZFQ*F;Ni!Bd|j0 z-wQ|>z~*V&xhlC{-f_6yw)*Gs$5ot)n=clAM4Rh!|H2*aX5r3@^tXgoAwB4jO;Sz( z%@wNJgfrn$1C*N)T$jM2va-Eac=s!AJNf4)K5&SzWN^QI?nKI%!OEevCsKL$Z3RzE zzbn{yTjzE{^!M;Z?}l*k($S;OH@3h%vQ0Q?L6$ug=!;wegS zkAbBnMw~tgGo^(0V>u73w=+6ri9i0BBMsy6vjQ zUbE;Qi9x7VVC~;%c>Y20`efm0bF>h04LR}cg;Wt|gJLns~3w@ZOn40yy+} za4MNYY+yi@pU;|Gp^p)YtHYYE!1GL@c<4IoZ8RQ3-JHStUNfv5SrYqdGKLk*j&Gn4 z105tp&P|C=6%lFk*5G%tys3-5fgyEXNxf!cXnRJqW=S}VR~iR{f&y9oxJS=cWrsmQ zEa-uJ>!fNiw0IctQBR)3VKP{*Unhj5F`TfR(qSFj`;o?}qMDC@n2%N!KRybLE&U11 z-b@}c1_q289$n3)(h#E3lD@a4-=as2*o-LjxG~_S32o}MLP><$MU0-&`135gx}~2j zTBM-y=bjcj&jFUu?;XQ%2-SDd z;J^##z=ZGwrq;kFopRe+u$m?5n~Q=AtSe~n6LF?U8l3xgXjDB}=U=W!78Sx5PA|sA zCq$mU@hs{#i^dOO(eN?h8e4hdBiZ(3k@@GhNIJG?N_`>1E2eafWzaTsdC(zCChQDx z05?K^kt1T71RQ35-3EC=U-ew2DOw6vpTvn5MS1SJWS}h9O?_^1n@QHHJCgaV&sZ`8 zW|$MZFrm|6^sk07pDJwMrb z7A7-TN;k^Em^4cDL3q9EyZ*x#;#fwj9QRM!mD%nj(H8)r#eW+H^Z`HnT{l9GBv=Q_ z8)NDVy5x-;n1M}9#l^*S-tiR@*{@$G5YPx*7Z<0+j+{?;QI@*S{kBNQWO;t%oh}z8 zezf9Ox^7uKp11UKr?mc;Xqec0WX0dVYwU{b`RVGW#c~}zGHKuVQ!V^xAloVM#~}n3 zG=%)KaZv!H*E4wD>VAk4YgSIWS@rQ*>dy!)@p$9FkJjJzGor$HIFusE@r;Y^8RteS z$Jv`RL&R%k<_T*IHjqb;$Fw1`@oYX1F4~bK!0q86tLnG`i&LAX*cE4Gpt6UocE79zXio78^Q8MePam790+ zb;$s_^2cKy{pz@|zzw4K(&G_Kse+Epw}QHZNKBP^5a%|hcAWQq0gLD5#4W-biV4^_ z@*K#9V( zoE)78LcCB#+2J}haKm=+SLnPXP38TmCDocQx>n=JxuGRgti=a@sEm> zKk>AWgVUP!3s{qc>aQ=$?YAFA>xB3Cm93vWF|))aGoKF%_la0E_F!%woo5?MqD<1| z$Ox|8J7cjlsF@ARUgy2vsLf}bLe;W;qF%m!q10Tlz7^{+nd)w_6dKgLAm_RQ1*-HB zx9fdvIecOTDfy z?|h1!)q~nMA6y^%*Sp9JiP8OI&;QR_+0%LQ??uelGJb~t**j%Ii!PcL9OgeiAEQwX z{XZL+{|{|=vSH8Mx`{b!^agc$aJW6482nXw+)eJQ!RSTtz^lu=p&;a7%uGv|z`@K# zb)G^?Odd7ye@BB7zl-EU`1}9)zPjh(@X5|>2`|?p7Tt1m`0UsJ?`M$});ACzdjB($ zs59FoqkHoI{SGWw<}L6)~d?5 z(s8teKDdz5?Ktp?5+AGic!A)don4!i&>RGvm(l@yTH-bdQkTJ(vL{OvOMn07mz3b2 zoSeLr3zJ>)JRy>`!JpR%?K9w^5-_VnG+Bg2raaYWzMN`#cQ>=$v8LVOcEY_>CK=Zq zva5@^Rqw+QIN7=awNy`GVPP{(O+vqeRlTibv6;Gmc54U7dOz6oS2cy9m*2QD+_c7V zR7=iR@+9=?HXV_e-{JG!6mvd=>Owp`Aa z5>o>s88a<$ak#F1FZid#W`n){bHMW^KSn9vCG8x^E~0BUqwrT-+TeHv=o9PSBP4Uo za?w#y?hHL1+}zv?PLL(wpV>yGO$+}$|1Aotp|I37M_C<>cTfBJ`u2~}@a~&;%2d0!wB7VfqDpe^pDy#J0wV|Y;vE1$`^lzID5_6fjoh|NxF6Fn? zNEWO17&B~pmNPdv;sw7c>&=_wsWL~j@3nIhitiQ8#3@}&LX(Qf+xmJs1StapU$g3^ zC|&aXM!w}!rR^TMEZ(Lt2nUKVmcov|xZD~1LP(zK3*O&9ft@p?Fw3R5HJ|*gXJmdC zdj8d~x3|~-=+C3w@4OXLbr^-W<63f?VD98MHZp*5vGoatKU_&+*Qq*sugb7{;4Hz0 z(`6MDBw1TqTX2FuzV-J}qah`2)p~t9o`9J6FT&kr^2@Szu55qE z<;e=_Lu0*TK}Gt-op$*J1z(2?)Vv^sn|XWR=6u%cdKxfQ{!DH*$F?VBIU*uL9AUOH zkn^=KQ_!oTaqqj9Tx^!~^Z`a4JI5={?vT^9A~O{gtowR;ZK!1k6N~}jI-IXEm9LT* z{NROl*mjUu(2;Yw=wz*1%i4;&luq+Z%X_UXPnezd)vs>RB`++6hQV@f3WFs%`S~%$ zeG7|$YssoxKi}P*I@@j;EFS%r6-r#YzBK4pbf)|a_loJ69MTQGQnp3KN|Gv@-`13b z>*>K-Z3uau@^R$<1X{(sW5l8Sz5m8~U9M1htlR&0U|hg4_}{$YyWyvhGhbsqYT9H~ zkJmTtzW2-Wp@OX$=R>hG1bl~OBdhMDYsbfnsp~m{85%%exXqF2p*936u%XhBC!;aM z7}RpI>wA$0Ve{U5%_k6)A$WM~E~q@u%(OlHHyW3mOf~j+x?bdHa}p=AEgY`uJS?{k z_z?{eA0Iy_PTbbkwy?LqGS~yd*A>Re$!XjFT7-+AU&Yr~6v7-97+w%B#XVS4m}C;h zEW;St+1a@S1iF6}-;+rRI6vMM_uZf^DJeP0R*;gDM^rSQ;=8q6hz26Jl4V0quZxOOuMFn) z49Z;*hNtRQm{en%*1D?r`-|HTWXJa~hfS@76dUk3PuEjHAXW+vmOee&Ox#`UvE5th zUAXBo@%c%YjRO34cQSn}iR>`~)-ocTj7@b8AVSw&!KSFRG*VbmR6VK8Y5@Tfzst=N z;d>`cUXuyDjSro>t3!EC-tSN&or|OPU&K(q_xS8+(@aZ?sAz1x>+#9n@>Gdosq_n& zi)OH)D~vZoV_@Fh9ahgg%&QJVBO}k7uQan;+~*Zt;SCH30b$|vMe!6^TBwtg%SGh} zoN)SE_Jl|n(}0cb-5%zZFV273(dIa`v%kTx1^kc;yOj2W;OL2{J6s?0a%Nk_=#S{fH+1S`HU?<I;rsW2IXxZ>%@;?eny+u;y?XU(yv~D7 z5JGNIYzFq9!gSrTaPey|)HD&;;|~v%?d} zg1IT%`-5^lF#U%sEal)6%BrfYGBW1bSBGPki!(vnUACz*f79#KQRtNIKXKzb(L&9b zMF)iUN_dhIEg?38geRs_3?UjqoB>2b338=aVigF9Ga*Rx;ETgCrggc~)dE0AH(_1v z{JN(Tj~9n<>!H2vK#EPw5Vx`642tYIga#(+lo;ctKoN`36T4u@S074B+5vYBm77a> zTrT%zdK~XqJJdY*Xj;2PXGR1o?_`A)%`FB6@xIR{LE5zW^+AtT$f~tpmJ;r%YZ(#^ zrJ-h)=%zh0Di}=}e872)ijK)ov$K<`YReavL()#&FRbkAN2LZqCsAwyfD_TYT EU$#aDWB>pF diff --git a/lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png b/lib/gaborator/doc/gen/allkernels_v1_bpo12_ffmin0.03_ffref0.5_syn_wob.png deleted file mode 100644 index 03ff1a1d3d723c0808944ef5462778101db815cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109213 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

Q4&L~2G(|99G#-S%h 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 YuNv9qu|)kE9|I70y85}Sb4q9e0156ZLjV8( diff --git a/lib/gaborator/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png b/lib/gaborator/doc/gen/grid_v1_bpo12_ffmin0.03_ffref0.5_wob.png deleted file mode 100644 index 91fdf7b59d311326d84b5137456b81d4dc67437a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14556 zcmeHu1yoe+xA!Qo7^s+lQU>vggoGjuDk2PuB8W&SjY!E5f-nN2B1lO{gGdWVmkgp( z(%r2?mku?+@a+TktNy=x@Bdrtes|rq7|y_n=h?mYZ|~!B{+!g7O?x+?P^c}^XHLnZ zP-|;Ys5P%Qki&n9BS>Cw5Wq-Y##}Vf#aLatse?Lu4P$!41arecbDyQoO>+YiV_r^P zPCoX1`WTFXLOZBwYog@+6i!>vjH7EWKvPPkfOG}@UP_Paj z>%?MwK7=eUH#41FyFvOdzUz{UjcjKdtuI>`UQ_e%8#imO&%J7UqnWX2<}x~ABsqgN zmfLaW74ZVU1la>ujyy%o?lDqq1XKaJWQT$1bI2jxn{`c^2Vu1pN zjNYT9v9>ni)B)sYX2w!cNl9fQ@8$=YvCcdtGBUD&sR1l$K$I2KPP~RK*CA94i;I>E zONx$WIePS{L9yGO>jnmUK9Xj;Y?F&U7{m5(;pU!t7_32|dK|55);RL%Rne*0j%5sw zj67!Qv}`xZ?ROVSzgpJRPV_6wBL0> z`KhI-cAV<&g-w`~C!JX2LZ6S1k5?FVc6J8x=sX-A9)7N2J{0B8=5=N;qglP)qFF8G zGW#WhS)J<RukCduAq&#I31Zx&gce3Ltpl!uw^ zv{D%`F*0i8$m)>Cr-4fT?IGodE~c^jpt5_xX2g>%)4^C@zh-8v-Qx8|R{j3sN8ZnN zo;xnFJd0h9ArfuII&#u_nl&sM6>|x7=;uqjTql+z8F-0Q92mjC1##gn?i0-QkU395pDeXe54_XLaAM9WL5 zs00lS5A!^F^e8$dr50mh5io&WUT9iS$J9#dF3wGz_6|5^6nJYSF{@$=f5-WcS^Aasmmk1`3a=h{%`>R7$>U)@d7DY`6K~5gVD5&! zcIa!nZMX2j_ak(C;0955*8dj|Q{iN%I^$T7JHSX4m7l^%U@|ImHdYt#w4d*VgOy0+%N5kpKG+ z`xr2HVpSS(Qeghx&l=IQhka#+yc47-ZMRCl3DkOq-;dVOM(B*XZSLvJZVyJioVq#7 zB<`=5Yt?jHDOxQ@bwII?jsAh4Q)FXKgcIkM_cYLH?|1Ds|H89XGxmE2=gG8WtHLBI zf9Ww~`D%3hWTG}P*ajTT_q@;h8w1)iOyi{KZEe#_rnR|X6L_o$@ossV)*!4M=eD8^J|RYRVu${#q}QQ{=)t+_LKBl=Vf=uaoxV^)~mE1h1$0Z?6iNkKB(C% zvc3QE;PlWqyniYiJc7>|CirRS>Pi9!?x?|QL(tC$FVF^FLGBO|HoW1|f$Fg2q57DF zxH##vXU_(5Yu&qY<;o`e%EYX$c&n~kB{6qlT~n{PhT8giE#1mMUx82&+ss=FV_ElK zE)T_o8LQil9pyF0%cj{aSju_mkc&}00OtbE>dm}Fx4gB&=;4|zTebuW-%8ER%j30Gm!@PPZ*u3hHAlA?@!8qrGUvN7(^<)LMD0C9T*rAbCe^W+B zspsw$c%R+Z3kgboAHOK^VmVI-;?iMnd6r||OpVRWddBEwyQ^3C4d6-`u`lseAqlj` z*%9yO+uPc9xfT&7c_Q!o2ULu;QsgNNB}-#jmi{QeqGL5nOH0FJV~XbH=J-rJUU!;6 zc%M%c)+*jiZ`pF~N`ZZT=8e{;B@DLUw=LRIbzC9txjpspMfB^w7SpdCS@gsFyC=Ob zC8@^U`h+(aYs>I{=I2M1csb?3gnfcia?@iu(EtI_h0(MONGp!=^B?v)v&$~HaAZ;o zOEkB3<9=AQ)%ceTc^CHK>VjWvLW@#3vq7QbI^x7K$;7DTg#_)f8#NSMFT>kS3e>zC zT3h3)s?JH*feH}1Y$tS1^91;CpPtB{4Hcd1adPzVLR^9Wfu*BE|H6&d_gK>Xokrk2 z0RnHX&DwRTcGVfE*Z$Xh!oR}{c#&j@e+uFMKSrB>ODG|*uHEt*dn)5;;3lsXU)Y!L zNr^wmEv5Oz$Nn5>XB@a$27Vu>2YJv@^6NbEE4o6TewMZOAF^uI&k09 zaE27+JS0VKO5xzrP?Y-r(L%m0Z+c?zmA+-<)#aM zGOrFp?92BwDpALu!5*mjlRwzDC7rU4V*aY=4)fnw_J6As<%_nkeiHbY;6JX!=3B?C zd-2tiTg~NY1#14`RGI%6JMxJyzbpn3I#>V24k}|up z^7GY1L`2$BwC;D^8ldllqG#{X>ps;kpl93Ce-}ER&*npcX7RC%H35fHhblpEEWMrp z)yV93l^w61I~AvDEDk3+Jv}{?*=}6FTS@g={=`UfLDFnDR?Pe*cG=!;v7d4FFdJ-` zZaWeZ_wL=xck81z1W-NWk@4{+pFunyekkIakt|!=aij8M$h*<|$-FGJL zaK9nNaO)XoK0&@RfL96<528PIo|;1~SHT1~uUk{gG?lOol$(LT;Vg-dYTGF%e1SCt zwHW9c>=pxHG^JM+iECc$cvhC;IFkb)g z&esM~L}qCunX+GK?~ad%*k2R8qxwC78`eN56{X;-LhGgJ=4r?@CIW@5k}Tx#k7)Ua z3)eGl-q*T@(=Ph9C6zSPB%hFEI`!0HP>oXgD}Lb%RQ}EYjzB)S!{t=rf-02uqERX4 zL%bjAJ?LGFwB5iv1ta)|C-W8X7!rPcNZlQNUW3f8a2@qv!$rBu95Bgq_071Mj5|A8 za|i(+5&x3+8_i>D*n)wI{g_Etuz5SaGAIKOmH)fj2zNK`{K(nc0`|yqE|}*0)7uDD z#FYB(Z9vz^_jC!b6qmzl$D0yWVhodVPFIU9-^OR|;nf`swH+_ZOFyXpGH5pnb$1;U z=n?iid!I_W5(XnZh9P}!qBVILfrzSE)%;OX-gxe+Y@UK_(wXc?pTe3ImDHjRU5wVR zk3M@P^G1OE$H!rR(I2K<5yoXi7({YG_R9}IpzKg;AbRGF8BHkxOH=MI@u<$RYm8T1 z!wgPT+{c-?doUpxcg3p1?0%Qc2*=Wt2ezWa*sV6*pnk2apNHPfuTME#)4k9&iy_Sq z42-_+-Rm#9qrTB&6SJ!=B9(9AsZ0HcolWhweBhPc4fEx&X4$`l~s2SqmM(~5(O|? zr|Fw5fR2^V^ZO?`Ff3|EJlV+H`$L&1`CH9L z*M$`MA_V09NI;)*s)rX>L!Rl~jT5?HY1g3axg4A9;PDj^5Y=0!E{~X=Hxh!{f zFNK8O!tYVDY}MJzZyel|q?S-zd^!Y6nwO9&H-!hJiVc<0oct-$6r~}#T7+?UH>k?l z5JAPT<;4LDM7016T%KZFjzFT_puRw=SS{<|*|M?|elI=%>{hxXkv-}CKJ@X_Y+zG5 z6M~R>h!XWzGy^uffNVDC$IS*8`QnNcVXcXRWzzoTv4@9~Cb`RMREeh;sl|pKup8@| zF$0!!!cSiZ`SMV&O!PGFyor|8K!_EdZpD4}I8qRLoG6D?x`~&><3?MyLbkAKamL|B zq>o(7^4*Jg{^^WlJC4D>1-=@Lt120XG?aVS_4PLhp597z0H7fXfQC-kZ6QGEYVKa* zPM?5yNC=6mX4HVFFci19?(0Jw1a}YEZ7}L0BB*zWF9+)qD1pjUo+n`Q-;YAjVT1j{ ztn^+*`3P6OvByB!aa=`}YM4AXCWduzgJcfDMU>ONZo6_v1(F5*ln%j~q#`c+3w12r zKyoOh!99E(D-kZbR{otinrfRV#?lV@tJ_F-&lU5h+e}|a!bu68xf|}IZ}K2%VWK&2 z0!NE^x!GT|wj@JN@W9Uo@mg7RNCoVUoLEg7ttK>Mmzs%{CiahW#-zlEUd9DWmWbZ0 zVG(*2B}XuudhA}>r;EhY?}O{8)_aO+QKX0tJZ|<>6!72*qbX}uNY5Z+TEkLaV*l{7 zqe<8Vkz{UZ0-GFUf>Tyhbc(s4s2Byg(9mqbvfZmPFr;mY5%%kBh8yCDWpegUU3Rh# zcUpBfUXY@X0wkWxe#*7#Xsz&LXn3reF0H=7q~btYwWtabExYVy)$-^1Tm}3w*^bTt z`@MF=MZGv*;Bwx3W}-V8V%GI;oSlFP3&t4@iA?SVA?1$pAK35X$2^ED(uu&46CWIO zui?VZqY;*hEzegoOH-?1y!^N{7)(>iRNFj-Z6hrqaBcoYY#`4~OS@PwUunVzN!wU^ zmYg@Cg17oFQsE&+-~9czYLHag%6uhXLfm1MFX+lj^316N66NZhtI!jTY)#V}c|$2y zF&gqjmgT5f-`hKnKio7pd9(T0b=KdBQ7PPNv?XHWPL}r_GbtW+vQX};i@SWZ!;pOp z=a=&LnZE}rsrrkEJMp@o;CV$~H#t!^zLHdjdL=?~M1Sxw2<&-cp5^u7} z0xI+P4|PubdEDB3>nTkRl}uT4e1#Vi^Nx)JqQcBUh^l2Gs#f`fs_pt5xo~TjYV}TW zik(D+OU)0$4f;7~AI4t{v^jslUAKO@G>Z~? ztHBm64-R4mgo;=Mmdwb^^z#W=+O-BHzLMs=QwoQ+NQYBsWJH9-V^+CKR#sN{%<*yK zvjIGk2n+|o(NjZ3MJ0i^TKJ+EY0AS@)E&KWC^xs7>32qbduQ(%8i|RqWO-B~nqeM$|JGnf_C?l=CzW?Z;lLC;DlR z-Ar0oWzJ|MjsMI=j9Hj;YIeYn0UJRD=(f$N+ekdJ7B=={SJs5?pES$kXh=`eC^33^ zF+-!HeBKpMUud~;>&}jMLjAOlw{BVe-BmHkl@=DwwLMU(Nd29aLUAzD3Bu=!g-dF@ zhOa2=-JmsZ-bO9$QtQ<7W*=V(`(NX~O!*E>cOQ?s11ImFHi*C$4xj?we=t%uXfI`_ zhaG^Twa?gnbhCDuB$ZHVOO7|ti{zx`QS3?3V*2wsnzTY;FX%UaP945fvuTckvC&d0jY+o27z@BaBb^nzaHDUTsH{dBX%A$Rh&^)G`O-E&(m_}^}LiyWzi zhP^!k;qN~E@Nze+w6+|AyO#g?pHA0i`P@@Ge*1Nv@@JP;%t@zH49i&0ru}1`Z=0l_ zu+LoGI6{DB12f~%lt`09TL6^=62DUQ-AT?O0CVraHok5QHmE73_VL0_p<#KtEtr5Q z<4ZzaOE*6M3r>y3qD8N3N_ETcwd|ezRteGMnfkQG&vZij*Vo6Me?%Rca!>ADT*sBi z=^a}h_A3g}$KSCp1l3!r#?@IIpB&d4K5&7W79I^teCFn$ip)u9Om&~u9 zmT3ST|=;$*gb)QD+MC2fP`Q_f>QC?ukBpslKICco#j z=Iw8~@l6rb9!4T<_3KuJ{W88uZyfkd&QrVOwwwnA`gRd$3+#cHF=vhxS>pTfR|Ww* z@>e#hY*`f;VOp_4XWP#v1*(qNThZKC2_TaAPUK@`oW)sRRsWly8lsMWL6Q$E=5Afz z^-@BOX;|~*&(;n``8+y0`ZK%9XMWEpwD#{Ab>+bC86{%7e0fCy(< ze_A~LTg}+rDP9dABLy`llF1N>L`=I;)(}+XFS@KM1CQGDxv-`iwI8eYc=J24DWz=r z%{0>KQL`4$4)-miW53|j#gr|CY%`~30%3M`rK<~=_<9kfQS8P|Y@cWvE>~ih+r5F- zn^Kd^_OsjlM!V>lD865$U{0Y0KjvJjXdhR@=bxS4H#vdxfxBmCXOVFyK>q{U1A~GZ zp=1D<-hEKZjKk~K?iOHk4(Y>6NewS=eQIcFi3M;aBRyTe=;7|Bg-c3G37MIfp!|ix zCq6N;5s;mSqU6mxTFu4+e&zO|rM00B@@qb`zEemO*PD>MOiV`5VP@%&kPz=@2}!v! zbv=GoSsgdgc*EJz)<_M~M}NWD4s(i?=jv$K>bga6Vo$6({O|2G5E>t1b3J5s147-k zR=7_gFR^I;Dgz@Uqw9u-v`zim8tAvlC%1-w2{Yf@TJ}TS-8WpSzc&%c+K_o8(W^o< zzq6E?H3gf#1+nj+j##o5+?Yw3`paO42}q%Lqi2kOn6VPZzNcz0q;RIclMNCI4PCwl zpYQSjB$V%UplR{-!Sn=~UsXC#dfb%8aGm-K_48~^o3@^o#O{dDm^&4AyF2@ue9iXY zBAc?JD|_}DGBb|o=s)*YeeN5Pa_J$=b}&vRY_l%3k%+r|c+zp&Q6Byq`K3uUvt~lq znff`k&%=zhT$j22-faj@>tEbau*u>7)g3cys^9ml8(9~nnfQvLi9)96%1S;X{MhLq zD}p$YiIraxhsg0)dz9V!nR_p<=7%dh_l11O?iH`VKbZ3$*aqX|f4_(BHQijjxWb9m9C2kft=vM#Ll>-$nMK(Gc#SX^K-Z5{G!RaJ~Hp+3B-rb2%C0Yj1M=v$+_ zeAJ2ZqCoQwyGaSQo>Qo&cfvSO?;OH7q9P!EJ6lyNT<nm_bQO>OQ~}@YB}b zuBfJFS2GK^yEsPiH$m&>>iP5l8G2~w>NY)B4(JCYEGVnnej$7!NMvgmCRmv-xtvC; z;^{3l{G@crI!oLTJ7rg%2XqD`e?U_EPIUKujDW>=K`e|G519-; zmYl1FzuF&!I4KRWH#V99xVP&EaL;wu?e4zRC*(py56%BPB}+MiGk{KP?gu}W+XB?! zDZOS&Zw@(8&JHDXCv6mbXpXr3cS&k_Owxdn21I8=|Jj@G$dMzX3(*-F8F3K5T=5nw zTPjj|ROb}3(2zH8l{=Jaa1eWf`HY^6l1JToM(?5z>=Mb;)P^UhZ2iKUWh|-uhgZM4 zx*px{DpFpgL|^K7d$;XIDJ-_Y#@4p6qa!#c5n4q^uN+d17ZVE?mQ7kmEBZz{7lk@g zFioq(51DKL%wBSnWq$VMovAHpXBKblM-M48*=Kr2rL)UVim9kl&pEe9Jj%>GR^QmD z15L-XT3V6Fq-^&xX_k_R%e>~caq;$^-aXH$PscfByq@H_%;hG(-Y{x#gRuD3gA5)) z%xJ$SFbxYh!qDL0k;cZx)7}V%05OlbGs~y8t!?kzJkM+1)!UqVbzZ2`L~U)7>AjF* zasco_)V)Z}t{7p_&f2BrmI4!G8xUWia7G%*PMw>k*VFl-(9 zq1@jP=8=IXF~@GSgePN{MkGp{$&jY3erK*lTZW-OG!>r%ILI^5E#<+G$lqmsvOUX$ z^6&ksri)W0BL$?90v}nHY2>-Zv?dkvvzj9tYL8lttL86$EN@HG^8ihLoRjafY~?OE z{CA>q+G%Jq*Fy_&dh!kB2z+K2b_MU46WS9g3^Q_6X~vy`=5M!4Jvr!p{PlB{tg8AM zE4M*+7#eFuy3}WQU_6_rV6KR(0+Nu5mK3eC(DxJo*z*ySd6TkU0B}}xp|zSkG+=wj zT*rW@9!S4ZU?5m4K0aO+#>NGPo;|)fo(6E`r_r?PK)|Tihx%`?h>efuNzR`MRL(Yy za4G4>?+lx}+nR3hI0r!WM+$9%ny0K(t$UsEt{RI^5wRIIJumA}f43~;Ax6qpPSbrP z@ZR%eMYdUg1vELYG2EUkd{uqu)pqMUXFNhf8F=CHiYdVVDq%#8-kS5CidNk-Y5aiL za?Y(DhxN~wNzf(@%*@PuWzOSB%au>?p2zbS2)r){vnLMWVVIp4aIa?$Nj=;a!wP98 z-J-nL8)7Sr_`7eX-I{6Dd!%rio|{bz06aWjP3BbXvK2J*d@FWxOXCwXmg46-C+w-` za{f9BaDdyd2Q;Wvj~wrh4l=H*3K7b%7`YT;*=8_2I9Qq|XJnLE1pstDaX4WO(_M;U z=n@&!McIJZKA2#=)Gz3U<6Oo+k%Gkd0mWwq)mA_bDoFA!RYR7Tq)!g6RU+ zq=Wk^XO^oFY~7VJ_Y8oU@yW@}MbsM4mlvy-Hy_b?0VRxnN4A+ax!5LQfEB0uOV)dC zNK#JM_kx~29}tg>0YHc9)~s@hrgN8d{}z#%7A<6B09Bud%yY;a%~?AjIx7%j-RIJl zWfFEN@d~qaE{QlYJ)Dp{oHGzgar}_ujM(xgF#z$iFz|_oVcf9A_pC?Z)daA{-D*#^2(=>Vo_%@*8Sc+axJ$q)r(AJ!KSY_7Sm|K z2G>@Xm>O1uinI$a5$Xl9EMU9>T2wC6^1YEbTo*WQXlL?d(PI~<@X+7jLl&)szFGye z;N6(as{{s2H%d8sAgV4r|Nb$a5LC*SU?@<}X<+&2x63C}L5D8ZX z3p|FMT0$G;Y|ybXn#Z#y1EID_TZKlCBx)3>BTZi|50U8HTtTDOR7t&Hv(qSp?di=L zST*dl5;h75QUx$P7ZPk_@O8JURIC5(@S9tvm#cuhl}v!WA+MMqt*f1^ z=QbZPd$D$+FH*RztxZ-*$=~3{4L;e8^{%$^3YOZ4-LV%IY-MEXN zbmmzF0j6FtI6AsnnEzllbPILHv-`KEfo&q6PoSAVNb$=BvyJPuV0SEDXP&6#Sq6aC z1_Eb=KAA)%9X!n^1qJ1NtX`O1=Cft1=yM_0LvMeSPJ@c}u`Ej^;OSaW_>qalgr6Rj z!y*aS`L#)3GWuT(q5U|L_T674OK-#$o$O&2Y&|_0YLXRRr$C8E(bCYAF51Be5fS>; z>EN}b>yquK5389Lu9J`?LtUPfzKiPdgw0(-oG581jC5%5Z`7UrrW@CzjwfYgC?Zal zj#i5>W`b22X-;m2w1E)GxO4{EWw8u4gZsx?Qce~ujhR@~NweyVV@vnnkA(rbpkqdL zwiEd=jHJo?Fu_gHr-0hG1MH_YsHD*d z9X#IIC3wS0@9dK!5;37R@$N$7#}QkBhQY=oBB;Bx&EjO@8Z3JNs13Kmlld)HVRRth|3bfqQ*wJ*J8{pw31|JhO1pELLG^LP^W#-s{y(TFV( QbPXjfdG1uwiK~wP2fS!zQUCw| diff --git a/lib/gaborator/doc/index.html b/lib/gaborator/doc/index.html deleted file mode 100644 index d7d3231..0000000 --- a/lib/gaborator/doc/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - -The Gaborator - - -

The Gaborator

-

The Gaborator is a library that generates constant-Q spectrograms -for visualization and analysis of audio signals. It also supports a -fast and accurate inverse transformation of the spectrogram coefficients -back into audio for spectral effects and editing.

- -

The Gaborator implements the invertible constant-Q transform of -Velasco, Holighaus, Dörfler, and Grill, described in the papers - -Constructing an invertible constant-Q transform with nonstationary Gabor frames, 2011 -and -A Framework for invertible, real-time constant-Q transforms, 2012, -using Gaussian bandpass filters and an efficient multi-rate architecture. -

- -

The Gaborator is written in C++11 and compatible with C++14 and C++17. -It has been tested on macOS, Linux, NetBSD, FreeBSD, and iOS, on Intel -x86_64 and ARM processors.

- -

The Gaborator is open source under the GNU Affero General Public -License, version 3, and is also available for commercial licensing. -See the file LICENSE for details.

- -

Example Code

- -

The following examples demonstrate the use of the library in -various scenarios. They are presented in a "literate -programming" style, with the code embedded in the commentary -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 .cc file in -the examples/ directory.

- - -

API Reference

-

The following documents define the library API. -

- - -

How it Works

-

The following document outlines the operation of the library.

- - -

FAQ

- - -

Contact

-

Email questions and bug reports to the author at info@gaborator.com.

- - - diff --git a/lib/gaborator/doc/overview.html b/lib/gaborator/doc/overview.html deleted file mode 100644 index cfb3857..0000000 --- a/lib/gaborator/doc/overview.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - -Overview of Operation - - -

Overview of Operation

- -

The Gaborator performs three main functions:

-
    -
  • spectrum analysis, which turns a signal into a set -of spectrogram coefficients -
  • resynthesis (aka reconstruction), which turns a -set of coefficients back into a signal, and -
  • rendering, which -turns a set of coefficients into a rectangular array of -amplitude values that can be turned into pixels to display -a spectrogram. -
- -

The following sections give a high-level overview of each -of these functions.

- -

Analysis

- -

The first step of the analysis is to run the signal through -an analysis filter bank, to split it into a number of -overlapping frequency bands.

- -

The filter bank consists of a number of logarithmically spaced -Gaussian bandpass filters and a single lowpass filter. Each bandpass -filter has a bandwidth proportional to its center frequency, which -means they all have the same quality factor Q and form -a constant-Q filter bank. The highest-frequency bandpass -filter will have a center frequency close to half the sample rate; in -the graphs below, this is simple labeled 0.5 because all frequencies -in the Gaborator are in units of the sample rate. The -lowest-frequency bandpass filter should be centered at, or slightly -below, the lowest frequency of interest to the application at hand. -For example, when analyzing audio, this is often the lower limit of -human hearing; at a sample rate of 44100 Hz, this means 20 Hz / 44100 -Hz ≈ 0.00045. This lower frequency limit is referred to as -the minimum frequency or fmin. -

- -

Although frequencies below fmin are assumed to not be of -interest, they nonetheless need to be preserved to achieve perfect -reconstruction, and that is what the lowpass filter is for. Together, -the lowpass filter and the bandpass filters overlap to cover the full -frequency range from 0 to 0.5.

- -

The spacing of the bandpass filters is specified by the user as an -integer number of filters (or, equivalently, bands) per octave. For -example, when analyzing music, this is often 12 bands per octave (one -band per semitone in the equal-tempered scale), or if a finer -frequency resolution is needed, some multiple of 12.

- -

The following plot shows the frequency responses of the analysis -filters at 12 bands per octave and fmin = 0.03. A more -typical fmin for audio work would be 0.00045, but -that would make the plot hard to read because both the lowpass filter -and the lowest-frequency bandpass filters would be extremely narrow.

- -Analysis filters - -

The output of each bandpass filter is shifted down in frequency to -a complex quadrature baseband. The baseband signal is then resampled -at a reduced sample rate, lower than that of the orignal signal but -high enough that there is negligible aliasing given the bandwidth of -the filter in case. The Gaborator uses sample rates related to the -original signal sample rate by powers of two. This means some of -frequency bands are sampled a bit more often than strictly -necessary, but has the advantage that the sampling can be synchronized -to make the samples of many frequency bands coincide in time, which -can be convenient in later analysis or spectrogram rendering. The -complex samples resulting from this process are the spectrogram -coefficients.

- -

The center frequencies of the analysis filters and the points in -time at which they are sampled form a two-dimensional, -multi-resolution time-frequency grid, where high frequencies -are sampled sparsely in frequency but densely in time, and low -frequencies are sampled densely in frequency but sparsely in time.

- -

The following plot illustrates the time-frequency sampling grid -corresponding to the parameters used in the previous plot. Note that -frequency was the X axis in the previous plot, but is the Y axis -here. The plot covers a time range of 128 signal samples, but -conceptually, the grid extends arbitrarily far in time, in both the -positive and the negative direction.

- -Sampling grid - -

Resynthesis

- -

Resynthesizing a signal from the coefficients is more or less the -reverse of the analysis process. The coefficients are frequency -shifted from the complex baseband back to their original center -frequencies and run through a reconstruction filter bank -that is a dual of the analysis filter bank. The following -plot shows the frequency responses of the reconstruction filters -corresponding to the analysis filters shown earlier.

- -Reconstruction filters - -

Although the bandpass filters may look similar to the Gaussian -filters of the analysis filter bank, their shapes are actually subtly -different.

- -

Spectrogram Rendering

- -

Rendering a spectrogram image from the coefficients involves -taking the magnitude of each complex coefficient, and then -resampling the resulting multi-resolution grid of magnitudes -into an evenly spaced pixel grid.

- -

Because the coefficient sample rate varies by frequency band, the -resampling required in the horizontal (time) direction also varies. -Typically, the high-frequency bands of an audio spectrogram have more -than one coefficient per pixel and require downsampling (decimation), -some bands in the mid-range frequencies have a one-to-one relationship -between coefficients and pixels, and the low-frequency bands -have more than one pixel per coefficient and require upsampling -(interpolation).

- - - - - diff --git a/lib/gaborator/doc/realtime.html b/lib/gaborator/doc/realtime.html deleted file mode 100644 index 5ea5bec..0000000 --- a/lib/gaborator/doc/realtime.html +++ /dev/null @@ -1,126 +0,0 @@ - - - - - -Is it real-time? - - -

Is it real-time?

- -

Several people have asked whether the Gaborator is suitable for -real-time applications. There is no simple yes or no answer to -this question, because there are many different definitions of -"real-time", and the answer will depend the definition. -Below are some answers to the question "is it real-time?" -rephrased in terms of different definitions.

- -

Can it processes a recording in less time than its duration?

- -

Yes. For example, at 48 frequency bands per -octave, a single core of a 2.5 GHz Intel Core i5 CPU can analyze some -10 million samples per second, which is more than 200 times faster -than real time for a single channel of 44.1 kHz audio.

- -

Does it have bounded latency? -Can it start producing output before consuming the entire input? -Will it stream?

-

Yes. See the streaming example. - -

Does it have low latency?

- -

Probably not low enough for applications such as live musical -effects. The exact latency depends on factors such as the frequency -range and number of bands per octave, but tends to range between -"high" and "very high". For example, with the parameters used in the -online demo, 48 frequency bands per octave down to 20 Hz, the latency -of the analysis side alone is some 3.5 seconds, and if you do -analysis followed by resynthesis, the total latency will -be almost 13 seconds.

- -

This can be reduced by choosing the analysis parameters for low latency. -For example, if you decrease the number of frequency bands per octave to 12, -and increase the minimum frequency to 200 Hz, the latency -will be about 85 milliseconds for analysis only, and about -300 milliseconds for analysis + resynthesis, but this is -still too much for a live effect.

- -

Any constant-Q spectrum analysis involving low frequencies will -inherently have rather high latency (at least for musically useful -values of Q), because the lowest-frequency analysis filters will have -narrow bandwidths, which lead to long impulse responses. Furthermore, -the Gaborator uses symmetric Gaussian analysis filters that were -chosen for properties such as linear phase and accurate -reconstruction, not for low latency, so the latency will be higher -than what might be achievable with a constant-Q filter bank -specifically designed for low latency.

- -

The latency only affects causal applications, and -arises from the need to wait for the arrival of future input samples -needed to calculate the present output, and not from the time it takes -to perform the calculations. In a non-causal application, -such as applying an effect to a recording, the latency does not apply, -and performance is limited only by the speed of the calculations. -This can lead to the somewhat paradoxical situation that applying an -effect to a live stream causes a latency of several seconds, but -applying the same effect to an entire one-minute recording runs in a -fraction of a second.

- -

In analysis and visualization applications that don't need to -perform resynthesis, it is possible to partly hide the latency by -taking advantage of the fact that the coefficients for the higher -frequencies exhibit lower latency than those for low frequencies. -For example, a live spectrogram display could update the -high-frequency parts of the display before the corresponding -low-frequency parts. Alternatively, low-frequency parts of the -spectrogram may be drawn multiple times, effectively animating -the display of the low-frequency coefficients as they converge to -their final values. This approach can be seen in action in -the Spectrolite -iOS app.

- -

Does it support small blocks sizes?

- -

Yes, but there is a significant performance penalty. -The Gaborator works most efficiently when the signal is processed -in large blocks, preferably 217 samples or more, -corresponding to several seconds of signal at typical audio sample -rates.

- -

A real-time application aiming for low latency will want to -use smaller blocks, for examples 25 to 210 -samples, and processing these will be significantly slower. -For example, as of version 1.4, analyzing a signal in blocks of -210 samples takes roughly five times as much CPU as -analyzing it in blocks of 220 samples.

- -

For sufficiently small blocks, the processing time will exceed the -duration of the signal, at which point the system can no longer be -considered real-time. For example, analyzing a 48 kHz audio -stream on a 2.5 GHz Intel Core i5 CPU, this happens at block sizes -below about 24 = 16 samples.

- -

The resynthesis code is currently less optimized for small block -sizes than the analysis code, so the performance penalty for -resynthesizing small blocks is even greater than for analyzing small -blocks.

- -

Can it process a signal stream of any length?

- -

Not in practice — the length is limited by floating point -precision. At typical audio sample rates, roundoff errors start to -become significant after some hours.

- -

Does it avoid dynamic memory allocation in the audio processing path?

- -

Currently, no — it dynamically allocates both the coefficient data -structures and various temporary buffers.

- - - - - diff --git a/lib/gaborator/doc/ref/gaborator_h.html b/lib/gaborator/doc/ref/gaborator_h.html deleted file mode 100644 index 206901e..0000000 --- a/lib/gaborator/doc/ref/gaborator_h.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - - -Gaborator reference: gaborator.h - - -

Gaborator reference: gaborator.h

- -

Spectrum Analysis Parameters

- -

A parameters object holds a set of parameters that -determine the frequency range and resolution of the spectrum -analysis.

- -
-class parameters {
-
- -
-

Constructor

-
-parameters(unsigned int bands_per_octave,
-           double ff_min,
-           double ff_ref = 1.0);
-
-
-
bands_per_octave
-
The number of frequency bands per octave. - Values from 4 to 384 (inclusive) are supported. -
-
ff_min
-
The lower limit of the analysis frequency range, in units of the - sample rate. The analysis filter bank will extend low enough in - frequency that ff_min falls between the two lowest - frequency bandpass filters. - Values from 0.001 to 0.13 are supported.
-
ff_ref
-
The reference frequency, in units of the sample rate. - This allows fine-tuning of the analysis and synthesis filter - banks such that the center frequency of one of the filters - is aligned with ff_ref. If ff_ref - falls outside the frequency range of the bandpass filter bank, this - works as if the range were extended to include - ff_ref. Must be positive. A typical value - when analyzing music is 440.0 / fs, where - fs is the sample rate in Hz. -
-
-

Comparison

-

-Comparison operators are provided for compatibility with -standard container classes. The ordering is arbitrary but consistent. -

-
-bool operator<(const parameters &rhs) const;
-bool operator==(const parameters &rhs) const;
-
- -
-
-};
-
- -

Spectrogram Coefficients

- -
-template<class T> class analyzer;
-
- -

-A coefs object stores a set of spectrogram coefficients. -It is a dynamic data structure and will be automatically grown to -accommodate new time ranges, for example as newly recorded audio is analyzed. -The template argument T -must match that of the analyzer (usually float). -The template argument C is the data type used to store each -coefficient value; there is usually no need to specify it explicitly as -it will default to std::complex<T>. -

- -
-template<class T, class C = std::complex<T>>
-class coefs {
-
-
-

Constructor

-
-coefs(analyzer<T> &a);
-
-

-Construct an empty set of coefficients for use with the spectrum -analyzer a. This represents a signal that is zero -at all points in time. -

- -
-
-};
-
- -

Spectrum Analyzer

- -

-The analyzer object performs spectrum analysis and/or resynthesis -according to the given parameters. The template argument T is -the floating-point type to use for the calculations. This is typically float; -alternatively, double can be used for increased accuracy at the -expense of speed and memory consumption.

- -
template<class T>
-class analyzer {
-
- -

Constructor

- -
-analyzer(const parameters &params);
-
-
-
params
-
The spectrum analysis parameters. -
- -

Analysis and synthesis

- -
-void
-analyze(const T *signal,
-        int64_t t0,
-        int64_t t1,
-        coefs<T> &coefs) const;
-
-

Spectrum analyze the samples at *signal and add the -resulting coefficients to coefs. -

-
signal
-
The signal samples to analyze, beginning with the sample from time t0 - and ending with the last sample before time t1, for a total of - t1 - t0 samples. -
t0
-
The point in time when the sample at signal[0] was taken, - in samples. For example, when analyzing an audio recording, this is typically - 0 for the first sample in the recording, but this reference point is arbitrary, - and negative times are valid. Accuracy begins to successively decrease - outside the range of about ±108 samples, so using - large time values should be avoided when they are not necessary because - of the length of the track. -
-
t1
-
The point in time of the sample one past the - end of the array of samples at signal, - in samples. -
-
coefs
The coefficient object that the results of the - spectrum analysis are added to. -
-

If the coefs object already contains some -coefficients, the new coefficients are summed to those already -present. Because the analysis is a linear operation, this allows a -signal to be analyzed in blocks, by making multiple calls -to analyze() with non-overlapping ranges that together -cover the entire signal. For efficiency, the blocks should -be large, as in -analyze(first_131072_samples, 0, 131072, coefs), -analyze(next_131072_samples, 131072, 262144, coefs), -etc. -

- -
-void
-synthesize(const coefs<T> &coefs,
-           uint64_t t0,
-           uint64_t t1,
-           T *signal) const;
-
-

Synthesize signal samples from the coefficients coef and store -them at *signal. -

-
-
coefs
The coefficients to synthesize the signal from.
-
t0
-
The point in time of the first sample to synthesize, - in samples, using the same time scale as in analyze().
-
t1
-
The point in time of the sample one past the last one to synthesize.
-
signal
-
The synthesized signal samples will be written here, - beginning with the sample from time t0 and - and ending with the last sample before time t1, - for a total of t1 - t0 samples.
-
-

The time range t0...t1 may extend outside -the range analyzed using analyze(), in which case the -signal is assumed to be zero in the un-analyzed range.

- -

A signal may be synthesized in blocks by making multiple calls to -analyze() with different sample ranges. For efficiency, -the blocks should be large, and each t0 should -be multiple of a large power of two.

- -

Frequency Band Numbering

- -

The frequency bands of the analysis filter bank are numbered by -nonnegative integers that increase towards lower (sic) frequencies. -There is a number of bandpass bands corresponding to the -logarithmically spaced bandpass analysis filters, from near 0.5 -(half the sample rate) to -near fmin, and a single lowpass band containing the -residual signal from frequencies below fmin. -The numbering can be examined using the following methods: -

- -
-int bandpass_bands_begin() const;
-
-

-Return the smallest valid bandpass band number, corresponding to the -highest-frequency bandpass filter.

-
-int bandpass_bands_end() const;
-
-

-Return the bandpass band number one past the highest valid bandpass -band number, corresponding to one past the lowest-frequency bandpass -filter. -

-
-int band_lowpass() const;
-
-

-Return the band number of the lowpass band. -

-
-int band_ref() const;
-
-

-Return the band number corresponding to the reference frequency -ff_ref. If ff_ref falls within -the frequency range of the bandpass filter bank, this will -be a valid bandpass band number, otherwise it will not. -

-
-double band_ff(int band) const;
-
-

-Return the center frequency of band number band, in units of the -sampling frequency. -

- -

Support

-
-double analysis_support() const;
-
-

Returns the one-sided worst-case time domain support of any of the -analysis filters. When calling analyze() with a sample at time t, -only spectrogram coefficients within the time range t ± support -will be significantly changed. Coefficients outside the range may change, -but the changes will sufficiently small that they may be ignored without -significantly reducing accuracy.

- -
-double synthesis_support() const;
-
-

Returns the one-sided worst-case time domain support of any of the -reconstruction filters. When calling synthesize() to -synthesize a sample at time t, the sample will only be -significantly affected by spectrogram coefficients in the time -range t ± support. Coefficients outside the range may -be used in the synthesis, but substituting zeroes for the actual -coefficient values will not significantly reduce accuracy.

- -
-
-};
-
- -

Functions

- -

Iterating Over Existing Coefficients

- -
-template <class T, class F, class C0, class... CI>
-void process(F f,
-             int b0,
-             int b1,
-             int64_t t0,
-             int64_t t1,
-             coefs<T, C0> &coefs0,
-             coefs<T, CI>&... coefsi);
-
- -

-Process one or more coefficient sets coefs0... by applying -the function f to each coefficient present in coefs0, -in an indeterminate order.

-

-

This can be optionally limited to coefficients whose -band number b and sample time t satisfy -b0b < b1 and -t0t < t1. -To process every coefficient present -in coefs0, pass INT_MIN, INT_MAX, INT64_MIN, INT64_MAX -for the arguments b0, b1, t0, -and t1, respectively. -

-

The function f should have the call signature

-
-
-template<class T>
-void f(int b, int64_t t, std::complex<T> &c0, std::complex<T> &ci...);
-
-

where

-
-
b
-
The band number of the frequency band the coefficients - c0 and ci... pertain to. - This may be either a bandpass band or the lowpass band.
-
t
-
The point in time the coefficients c0 and - ci... pertain to, in samples
-
c0
-
A reference to a complex coefficient from coefs0
-
ci...
-
Optional references to complex coefficients from the additional - coefficient sets coefsi....
-
-
- - -

The function f may read and/or modify each of the -coefficients passed through c0 and each -ci....

- -

The first coefficient set c0 is a special case when -it comes to the treatment of missing values. Coefficients missing -from c0 will not be iterated over at all, but when a -coefficient is iterated over and is missing from one of the additional -coefficient sets ci..., it will be automatically created -and initialized to zero in that additional coefficient set.

- -

Note: The template parameters C0 -and CI... exist to support the processing of coefficient -sets containing data of types other -than std::complex<T>, which is not currently part of the -documented API. In typical use, there is no need to specify them when -calling apply() because the template parameter list -can be deduced, but if they are expicitly specified, they should all -be std::complex<T>. -

- -

Creating New Coefficients

- -
-template <class T, class F, class C0, class... CI>
-void fill(F f,
-          int b0,
-          int b1,
-          int64_t t0,
-          int64_t t1,
-          coefs<T, C0> &coefs0,
-          coefs<T, CI>&... coefsi);
-
-

-Fill a region of the time-frequency plane with coefficients -and apply the function f to each. -

-

This works like process() except that it is not limited -to processing coefficients that already exist in coefs0; -instead, any missing coefficients in coefs0 as well as -any of the coefsi... are created and initialized to zero -before f is called.

- -

The t0 and t1 arguments must specify an -explicit, bounded time range — they must not be given as -INT64_MIN and/or INT64_MAX as that would mean creating coefficients -for an an astronomically large time range, requiring a correspondingly -astronomical amount of memory.

- -

Forgetting Coefficients

-
-template <class T>
-void forget_before(const analyzer<T> &a,
-                   coefs<T> &c,
-                   int64_t limit);
-
-

Allow the coefficients for points in time before limit -(a time in units of samples) to be forgotten. -Streaming applications can use this to free memory used by coefficients -that are no longer needed. Coefficients that have been forgotten will -read as zero. This does not guarantee that all coefficients before -limit are forgotten, only that ones for -limit or later are not, and that the amount of memory -consumed by any remaining coefficients before limit is -bounded.

- -

Legacy API For Iterating Over Existing Coefficients

- -

Prior to version 1.5, the only way to iterate over -coefficients was the apply() function. -It is similar to process(), except that it -

-
    -
  • requires an additional analyzer argument, -
  • takes arguments in a different order, -
  • applies a function f taking arguments in a different order, -
  • does not support restricting the processing to a range of band numbers, -
  • only supports iterating over a single coefficient set, and -
  • provides default values for t0 and t1. -
-

In new code, process() is preferred.

- -
-template <class T, class F>
-void apply(const analyzer<T> &a,
-           coefs<T> &c,
-           F f,
-           int64_t t0 = INT64_MIN,
-           int64_t t1 = INT64_MAX);
-
-

-Apply the function f to each coefficient in the coefficient -set c for points in time t that satisfy -t0t < t1. -If the t0 and t1 arguments are omitted, f -is applied to every coefficient. -

-
-
a
-
The spectrum analyzer that produced the coefficients c
-
c
-
A set of spectrogram coefficients
-
f
-
A function to apply to each coefficient in c, - with the call signature -
-template<class T>
-void f(std::complex<T> &coef, int band, int64_t t);
-
-
-
coef
-
A reference to a single complex coefficient. This may be read and/or modified.
-
band
-
The band number of the frequency band the coefficient coef0 pertains to. - This may be either a bandpass band or the lowpass band.
-
t
-
The point in time the coefficient c0 pertains to, in samples
-
t0
When not INT64_MIN, only apply f to the coefficients for time ≥ t0
-
t1
When not INT64_MAX, only apply f to the coefficients for time < t1
-
-
-
- - - - - diff --git a/lib/gaborator/doc/ref/intro.html b/lib/gaborator/doc/ref/intro.html deleted file mode 100644 index d45d775..0000000 --- a/lib/gaborator/doc/ref/intro.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - -Gaborator reference: API Introdution - - -

Gaborator reference: API Introduction

- -

The public API of the Gaborator library is defined in the HTML -documentation in the form of annotated C++ declarations. These are -similar to the actual declarations in the respective header files, but -simplified for clarity and omitting implementation details.

- -

The actual implementation in the header file may be different in a -number of ways but nonetheless compatible with the documented API. -For example, classes may be declared using the keyword struct -rather than class, function parameter names may be -different, types may be declared using different but equivalent -typedefs, and functions or templates in the header file may have -additional arguments with default values. Any classes, functions, and -other definitions not mentioned in the documentation should be -considered private and are subject to change or deletion without -notice. -

- -

All definitions are in the namespace gaborator. -Applications need to either prefix class names -with gaborator::, or use using namespace -gaborator;. -

- - - - - diff --git a/lib/gaborator/doc/ref/render_h.html b/lib/gaborator/doc/ref/render_h.html deleted file mode 100644 index 2ea3351..0000000 --- a/lib/gaborator/doc/ref/render_h.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - -Gaborator reference: render.h - - -

Gaborator reference: render.h

- -

Spectrogram Rendering with Power-of-Two Scaling

-
-template <class OI, class T>
-void render_p2scale(const analyzer<T> &a,
-                    const coefs<T> &c,
-                    int64_t xorigin, int64_t yorigin,
-                    int64_t xi0, int64_t xi1, int xe,
-                    int64_t yi0, int64_t yi1, int ye,
-                    OI output);
-
-

Render a rectangular array of pixel values representing signal -amplitudes in time-frequency space, optionally scaling up or -down by powers of two. -

-
-
a
-
The spectrum analyzer that produced the coefficients c
-
c
-
A set of spectrogram coefficients to render
-
xorigin
-
The point in time corresponding to pixel X coordinate 0, in samples
-
yorigin
-
The band number of the frequency band corresponding to pixel Y coordinate 0
-
xi0
-
The X coordinate of the first pixel to render
-
xi1
-
The X coordinate one past the last pixel to render
-
xe
-
The horizontal scaling exponent. One horizontal pixel corresponds to 2xe signal samples.
-
yi0
-
The Y coordinate of the first pixel to render
-
yi1
-
The Y coordinate one past the last pixel to render
-
ye
-
The vertical scaling exponent. One vertical pixel corresponds to 2ye frequency bands.
-
output
-
A random access iterator through which the output - pixel amplitude values will be written. This is - typically a float *. A total of - (xi1 - xi0) * (yi1 - yi0)) values will be written. -
-
- -

Utility Functions

-
-template <class T>
-unsigned int float2pixel_8bit(T amp);
-
-

Convert a normalized amplitude value to a 8-bit greyscale pixel value.

-
-
amp
-
A floating point value representing a signal amplitude, nominally ranging from 0 to 1
-
-

Returns an pixel value ranging from 0 to 255 (inclusive), using an -approximation of the sRGB gamma.

- - - - - diff --git a/lib/gaborator/doc/render.html b/lib/gaborator/doc/render.html deleted file mode 100644 index 981f954..0000000 --- a/lib/gaborator/doc/render.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - -Gaborator Example 1: Rendering a Spectrogram Image - - -

Example 1: Rendering a Spectrogram Image

- -

Introduction

- -

This example shows how to generate a greyscale constant-Q -spectrogram image from an audio file using the Gaborator library. -

- -

Preamble

- -

We start off with some boilerplate #includes.

- -
-#include <memory.h>
-#include <iostream>
-#include <fstream>
-#include <sndfile.h>
-
- -

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 -gaborator/gaborator.h, and the code for rendering -images from the spectrogram coefficients is in -gaborator/render.h.

- -
-#include <gaborator/gaborator.h>
-#include <gaborator/render.h>
-
- -

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:

- -
-int main(int argc, char **argv) {
-    if (argc < 3) {
-        std::cerr << "usage: render input.wav output.pgm\n";
-        exit(1);
-    }
-
- -

Reading the Audio

- -

The audio file is read using the libsndfile library -and stored in a std::vector<float>. -Note that although libsndfile is used in this example, -the Gaborator library itself does not depend on or -use libsndfile.

-
-    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: "
-            << sf_strerror(sf_in) << "\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);
-
-

In case the audio file is a stereo or multi-channel one, -mix down the channels to mono, into a new std::vector<float>: -

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

The Spectrum Analysis Parameters

- -

Next, we need to choose some parameters for the spectrum analysis: -the frequency resolution, the frequency range, and optionally a -reference frequency.

- -

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.

- -

The frequency range is specified by giving a minimum frequency; -this is the lowest frequency that will be included in the spectrogram -display. -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: 20.0 / fs.

- -

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.

- -

If desired, one of the frequency bands can be exactly aligned with -a reference frequency. When analyzing music signals, this is -typically 440 Hz, the standard tuning of the note A4. -Like the minimum frequency, it is given in -units of the sample rate, so we pass 440.0 / fs.

- -

The parameters are held in an object of type -gaborator::parameters: -

-    gaborator::parameters params(48, 20.0 / fs, 440.0 / fs);
-
- -

The Spectrum Analyzer

- -

Next, we create an object of type gaborator::analyzer; -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 float. -Constructing the gaborator::analyzer 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 gaborator::analyzer -and reusing it is preferable to destroying and recreating it.

-
-    gaborator::analyzer<float> analyzer(params);
-
- -

The Spectrogram Coefficients

- -

The result of the spectrum analysis will be a set of spectrogram -coefficients. To store them, we will use a gaborator::coefs -object. Like the analyzer, 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:

-
-    gaborator::coefs<float> coefs(analyzer);
-
- -

Running the Analysis

- -

Now we are ready to do the actual spectrum analysis, -by calling the analyze method of the spectrum -analyzer object. -The first argument to analyze is a float pointer -pointing to the first element in the array of samples to analyze. -The second and third arguments are of type -int64_t 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 mono.size(). The fourth argument is a -reference to the set of coefficients that the results of the spectrum -analysis will be stored in. -

-
-    analyzer.analyze(mono.data(), 0, mono.size(), coefs);
-
- -

Rendering an Image

- -

Now there is a set of spectrogram coefficients in coefs. -To render them as an image, we will use the function -gaborator::render_p2scale(). -

- -

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.

- -

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:

- -
-    int64_t x_origin = 0;
-    int64_t y_origin = analyzer.bandpass_bands_begin();
-
- -

render_p2scale() 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. - -

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 a spectrogram that is much -too stretched out horizontally. A more typical scale factor might be -210 = 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.

-
-    int x_scale_exp = 10;
-
- -

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:

-
-    while ((n_frames >> x_scale_exp) > 1000)
-        x_scale_exp++;
-
- -

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

-
-    int y_scale_exp = 0;
-
- -

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

- -
-    int64_t x0 = 0;
-    int64_t y0 = 0;
-
- -

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 n_frames, -and we get the number of bands as the difference of the end points of -the range of band numbers: -analyzer.bandpass_bands_end() - analyzer.bandpass_bands_begin(). -The scale factor is taken into account by right shifting by the -scale exponent. -

- -
-    int64_t x1 = n_frames >> x_scale_exp;
-    int64_t y1 = (analyzer.bandpass_bands_end() - analyzer.bandpass_bands_begin()) >> y_scale_exp;
-
- -

The right shift by y_scale_exp above doesn't actually -do anything because y_scale_exp is zero, but it would be -needed if, for example, you were to change y_scale_exp to -1 to get a spectrogram scaled to half the height. You could also make a -double-height spectrogram by setting y_scale_exp to -1, -but then you also need to change the ->> y_scale_exp to -<< -y_scale_exp since you can't shift by -a negative number. -

- -

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 -(y1 - y0) rows of (x1 - x0) columns -each, with the row indices increasing towards lower -frequencies and column indices increasing towards later -sampling times. -

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

Writing the Image File

- -

To keep the code simple and to avoid additional library -dependencies, the image is stored in -pgm (Portable GreyMap) format, which is simple -enough to be generated with just a few lines of inline code. -Each amplitude value in amplitudes is converted into an 8-bit -gamma corrected pixel value by calling gaborator::float2pixel_8bit(). -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.

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

Postamble

-

-To make the example code a complete program, -we just need to finish main(): -

-
-    return 0;
-}
-
- -

Compiling

-

-If you are using macOS, Linux, NetBSD, or a similar system, you can build -the example by running the following command in the examples -subdirectory. -You need to have libsndfile is installed and supported by -pkg-config. -

-
-c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` render.cc `pkg-config --libs sndfile` -o render
-
- -

Compiling for Speed

-

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 -GABORATOR_USE_VDSP and linking with the Accelerate -framework: -

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

On Linux and NetBSD, you can use the PFFFT (Pretty Fast FFT) library. -You can get the latest version from -https://bitbucket.org/jpommier/pffft, -or the exact version that was used for testing from gaborator.com: -

- -
-wget http://download.gaborator.com/mirror/pffft/29e4f76ac53b.zip
-unzip 29e4f76ac53b.zip
-mv jpommier-pffft-29e4f76ac53b pffft
-
-

Then, compile it:

-
-cc -c -O3 -ffast-math pffft/pffft.c -o pffft/pffft.o
-
-

(If you are building for ARM, you will need to add -mfpu=neon to -both the above compilation command and the ones below.)

-

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:

-
-cc -c -O3 -ffast-math -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o
-
-

Then build the example and link it with both PFFFT and FFTPACK:

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

Running

-

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 .pgm image, and then convert -the .pgm image into a JPEG image: -

-wget http://download.gaborator.com/audio/guitar.wav
-./render guitar.wav guitar.pgm
-cjpeg <guitar.pgm >guitar.jpg
-
- -

Example Output

-

The JPEG file produced by the above will look like this:

-Spectrogram - - - - - diff --git a/lib/gaborator/doc/snr.html b/lib/gaborator/doc/snr.html deleted file mode 100644 index 632c320..0000000 --- a/lib/gaborator/doc/snr.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - -Gaborator Example 4: Measuring the Signal-to-Noise Ratio - - -

Example 4: Measuring the Signal-to-Noise Ratio

- -

Introduction

- -

This example measures the signal-to-noise ratio (SNR) of the -resynthesis by analyzing and resynthesizing a test signal -and comparing the resynthesis result to the original. -

- -

Since it does not involve any audio file I/O, this example -does not require the sndfile library, making it the shortest -and simplest one by far.

- -

Preamble

- -
-#include <iostream>
-#include <iomanip>
-#include <random>
-#include <gaborator/gaborator.h>
-
- -

Amplitude Measurement

-

To calculate the signal-to-noise ratio, we need to measure the -amplitude of the orignal signal and the error residue. We will use -the root-mean-square amplitude, which is calculcated by the -function rms(). -

-
-double rms(const std::vector<float> &v) {
-    double sqsum = 0;
-    for (size_t i = 0; i < v.size(); i++) {
-        sqsum += v[i] * v[i];
-    }
-    return sqrt(sqsum);
-}
-
- -

Main Program

- -

For the test signal, we use a million samples of white noise with a -uniform amplitude distribution between -1 and +1.

-
-int main(int argc, char **argv) {
-    size_t len = 1000000;
-    std::vector<float> signal_in(len);
-    std::minstd_rand rand;
-    std::uniform_real_distribution<> uniform(-1.0, 1.0);
-    for (size_t i = 0; i < len; i++)
-        signal_in[i] = uniform(rand);
-
-

Then we create a spectrum analyzer with 48 bands per octave -and a frequency range of 3 decades (0.0005 to 0.5 times the sample rate):

-
-    gaborator::parameters params(48, 5e-4);
-    gaborator::analyzer<float> analyzer(params);
-
-

...and run the spectrum analyzis:

-
-    gaborator::coefs<float> coefs(analyzer);
-    analyzer.analyze(signal_in.data(), 0, len, coefs);
-
-

...resynthesize the signal into signal_out: -

-    std::vector<float> signal_out(len);
-    analyzer.synthesize(coefs, 0, len, signal_out.data());
-
-

...measure the resynthesis error:

-
-    std::vector<float> error(len);
-    for (size_t i = 0; i < len; i++)
-         error[i] = signal_out[i] - signal_in[i];
-
-

...calculate the signal-to-noise ratio:

-
-    double snr = rms(signal_in) / rms(error);
-
-

...and print it in decibels:

-
-    std::cout << std::fixed << std::setprecision(1) << 20 * log10(snr) << " dB\n";
-}
-
-

Compiling

-

Like Example 1, this example -can be built using a one-line build command: -

-
-c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` snr.cc `pkg-config --libs sndfile` -o snr
-
-

Or using the vDSP FFT on macOS:

-
-c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` snr.cc `pkg-config --libs sndfile` -framework Accelerate -o snr
-
-

Or using PFFFT (see Example 1 for how to download and build PFFFT):

-
-c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` snr.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o snr
-
- -

Running

-

The program is run with no arguments:

-
-./snr
-
-

This will print the SNR which should be more than 100 dB if the library is working correctly.

- - - - - diff --git a/lib/gaborator/doc/spectrogram.jpg b/lib/gaborator/doc/spectrogram.jpg deleted file mode 100644 index f854519447d3c7bb4a801201fc6fa6c8a8517d5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22542 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 diff --git a/lib/gaborator/doc/stream.html b/lib/gaborator/doc/stream.html deleted file mode 100644 index f270ecc..0000000 --- a/lib/gaborator/doc/stream.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - -Gaborator Example 3: Streaming - - -

Example 3: Streaming

- -

Introduction

- -

This example shows how to process streaming audio a block at a time, -rather than operating on a complete recording at once as in the previous -examples.

- -

This program doesn't do anything particulary useful — it just -inverts the phase of the signal, but not using the obvious method of -changing the sign of each sample but by changing the sign of each -spectrogram coefficient. Consider the expression coef = --coef a placeholder for your own streaming filter or effect -code.

- -

Preamble

- -
-#include <memory.h>
-#include <iostream>
-#include <sndfile.h>
-#include <gaborator/gaborator.h>
-
-int main(int argc, char **argv) {
-    if (argc < 3) {
-        std::cerr << "usage: stream input.wav output.wav\n";
-        exit(1);
-    }
-
- -

Opening the Streams

- -

We again use libsndfile to read the input and write the output. -To keep it simple, this example only handles mono files.

-
-    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: "
-            << sf_strerror(sf_in) << "\n";
-        exit(1);
-    }
-    if (sfinfo.channels != 1) {
-        std::cerr << "only mono files are supported\n";
-        exit(1);
-    }
-    double fs = sfinfo.samplerate;
-
-    SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo);
-    if (! sf_out) {
-        std::cerr << "could not open output audio file: "
-            << sf_strerror(sf_out) << "\n";
-        exit(1);
-    }
-
-

The next couple of lines work around a design flaw in -libsndfile. By default, when reading a 16-bit -audio file as floating point data and then writing them -as another 16-bit audio file, libsndfile will use slightly -different scale factors on input and output, and the output will -not be bit-identical to the input. To make it easier to verify -that this example actually yields correct results to within -the full 16-bit precision, we select a non-normalized floating -point representation, which does not suffer from this flaw.

-
-    sf_command(sf_in, SFC_SET_NORM_FLOAT, NULL, SF_FALSE);
-    sf_command(sf_out, SFC_SET_NORM_FLOAT, NULL, SF_FALSE);
-
- -

Spectrum Analysis Parameters

- -

As in Example 1, the parameters are chosen for analyzing music, but -to reduce the latency, the number of frequency bands per octave is reduced -from 48 to 12 (one per semitone), and the lower frequency limit of -the bandpass filter bank is raised from 20 Hz to 200 Hz.

-
-    gaborator::parameters params(12, 200.0 / fs, 440.0 / fs);
-    gaborator::analyzer<float> analyzer(params);
-
- -

Calculating Latency

-

The spectrogram coefficients are calculated by applying symmetric -FIR filters to the audio signal. This means a spectrogram coefficient -for any given point in time t is a weighted average of samples -from both before and after t, representing both past and future -signal. The width of the filter impulse response depends on the -bandwidth, which in turn depends on the center frequency of its band. -The lowest-frequency filters have the narrowest bandwidths, and -therefore the widest impulses response, and need the greatest amount -of past and future signal. The width of the filter impulse response -is called its support, and the worst-case (widest) support of -any analysis filter can be found by calling the function -gaborator::analyzer::analysis_support(). This returns -the one-sided support, the width of the impulse -response to each side of its center, as a floating point number. -To be on the safe side, let's round this up to the next integer:

-
-    size_t analysis_support = ceil(analyzer.analysis_support());
-
-

Similarly, when resynthesizing audio from coefficients, calculating -a sample at time t involves applying symmetric FIR -reconstruction filters, calculating a weighted average of both past and -future spectrogram coefficients. The support of the widest reconstruction -filter can be calculated by calling -gaborator::analyzer::synthesis_support(): -

-
-    size_t synthesis_support = ceil(analyzer.synthesis_support());
-
- -

In a real-time application, the need to access future signal -samples and/or coefficients causes latency. A real-time audio -analysis application that needs to examine the coefficients for -time t can only do so when it has received the input samples up -to time t + analysis_support, and therefore has a minimum latency of -analysis_support. A real-time filtering or effect -application, such as the present example, -incurs latency from both analysis and reconstruction -filters, and can only produce the output sample for time t once -it has received the input samples up to -t + analysis_support + synthesis_support, -for a minimum latency of analysis_support + synthesis_support. -Let's print this total latency to standard output: -

-
-    std::cerr << "latency: " << analysis_support + synthesis_support << " samples\n";
-
- -

In a practical real-time system, there will be additional latency -caused by processing the signal in blocks of samples rather than a -sample at a time. Since the block size is a property of the overall -system, and causes latency even if the Gaborator is not involved, that -latency is considered outside the scope of this discussion. -

- -

Streaming

-

To mimic a typical real-time system, the audio is processed -in fixed-size blocks (here, 1024 samples). If the size -of the input file is not divisible by the block size, the last block -is padded with zeroes. -The variable t_in keeps track of time, indicating -the sampling time of the first sample of the current input block, -in units of samples. -

-
-    gaborator::coefs<float> coefs(analyzer);
-    const size_t blocksize = 1024;
-    std::vector<float> buf(blocksize);
-    int64_t t_in = 0;
-    for (;;) {
-        sf_count_t n_read = sf_readf_float(sf_in, buf.data(), blocksize);
-        if (n_read == 0)
-            break;
-        if (n_read < blocksize)
-            std::fill(buf.data() + n_read, buf.data() + blocksize, 0);
-
-

Now we can spectrum analyze the current block of samples. Note how -the time range, -t_in...t_in + blocksize, -is explicitly passed to analyze(). -

-
-        analyzer.analyze(buf.data(), t_in, t_in + blocksize, coefs);
-
-

The call to analyze() updates the coefficients -for the time range from t_in - analysis_support to -t_in + blocksize + analysis_support. The oldest -blocksize samples of this time range, -that is, from t_in - analysis_support to -t_in - analysis_support + blocksize, were now updated for -the last time and will not be affected by future input blocks. -Therefore, it is now safe to examine and/or modify these -coefficients as required by your application. Here, by way -of example, we simply change their signs to invert the phase of the signal. -Note that unlike the earlier filter example where prorcess() -applied a function to all the coefficients, here it is applied only to -the coefficients within a limited time range. -

-
-        process(
-            [&](int, int64_t, std::complex<float> &coef) {
-                 coef = -coef;
-            },
-            INT_MIN, INT_MAX,
-            t_in - (int)analysis_support,
-            t_in - (int)analysis_support + (int)blocksize,
-            coefs);
-
- -

Next, we will generate a block of output samples. To get correct results, -we can only generate output when the coefficients that the output samples -depend on will no longer change. Specifically, a resynthesized audio -sample for time t will depend on the coefficients of the -time range t - synthesis_support...t + -synthesis_support. To ensure that the resynthesis uses only -coefficients that have already been processed by -the process() call above, the most recent block of samples -that can safely be resynthesized ranges from t_out = t_in - -analysis_support - synthesis_support to t_out + -blocksize.

-
-        int64_t t_out = t_in - analysis_support - synthesis_support;
-        analyzer.synthesize(coefs, t_out, t_out + blocksize, buf.data());
-
-

The synthesized audio can now be written to the output file:

-
-        sf_count_t n_written = sf_writef_float(sf_out, buf.data(), blocksize);
-        if (n_written != blocksize) {
-            std::cerr << "write error\n";
-            exit(1);
-        }
-
-

Coefficients older than t_out + blocksize - synthesis_support -will no longer be needed to synthesize the next block of output signal, so -it's now OK to forget them and free the memory they used: -

-
-        forget_before(analyzer, coefs, t_out + blocksize - synthesis_support);
-
-

This concludes the block-by-block processing loop.

-
-        t_in += blocksize;
-    }
-
- -

Postamble

-
-    sf_close(sf_in);
-    sf_close(sf_out);
-    return 0;
-}
-
- -

Compiling

-

Like the previous ones, this example can also be built using a one-line build command: -

-
-c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` stream.cc `pkg-config --libs sndfile` -o stream
-
-

Or using the vDSP FFT on macOS:

-
-c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` stream.cc `pkg-config --libs sndfile` -framework Accelerate -o stream
-
-

Or using PFFFT (see Example 1 for how to download and build PFFFT):

-
-c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` stream.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o stream
-
- -

Running

-

Running the following shell commands will download an example -audio file containing an impulse (a single sample of maximum amplitude) -padded with silence to a total of 65536 samples, and process it.

-
-wget http://download.gaborator.com/audio/impulse.wav
-./stream impulse.wav impulse_streamed.wav
-
- -

The file impulse_streamed.wav will be identical to -impulse.wav except that the impulse will be of -opposite polarity, and delayed by the latency of -analysis_support + synthesis_support samples.

- - - - - diff --git a/lib/gaborator/doc/synth.html b/lib/gaborator/doc/synth.html deleted file mode 100644 index edfa6f2..0000000 --- a/lib/gaborator/doc/synth.html +++ /dev/null @@ -1,213 +0,0 @@ - - - - - -Gaborator Example 5: Synthesis from Scratch - - -

Example 5: Synthesis from Scratch

- -

Introduction

- -

This example demonstrates how to synthesize a signal by creating -spectrogram coefficients from scratch rather than by analyzing an -existing signal. It creates a random pentatonic melody of decaying -sine waves as spectrogram coefficients and then synthesizes audio -from them. -

- -

Preamble

- -

This example program takes a single command line argument, the name -of the output file.

-
-#include <memory.h>
-#include <iostream>
-#include <sndfile.h>
-#include <gaborator/gaborator.h>
-
-int main(int argc, char **argv) {
-    if (argc < 2) {
-        std::cerr << "usage: synth output.wav\n";
-        exit(1);
-    }
-
- -

Synthesis Parameters

- -

Although this example does not perform any analysis, we nonetheless -need to create an analyzer object, as it is used for both -analysis and synthesis purposes. To generate the frequencies of the -12-note equal-tempered scale, we need 12 bands per octave; a multiple -of 12 would also work, but here we don't need the added frequency -resolution that would bring, and the time resolution would be -worse.

- -

To simplify converting MIDI note numbers to band numbers, we choose -the frequency of MIDI note 0 as the reference frequency; this is -8.18 Hz, which happens to be outside the frequency range of the -bandpass filter bank, but that doesn't matter.

- -
-    double fs = 44100;
-    gaborator::parameters params(12, 20.0 / fs, 8.18 / fs);
-    gaborator::analyzer<float> analyzer(params);
-
- -

Melody Parameters

- -

-We will use the A minor pentatonic scale, which contains the -following notes (using the MIDI note numbering):

-
-    static int pentatonic[] = { 57, 60, 62, 64, 67 };
-
- -

-The melody will consist of 64 notes, at a tempo of 120 beats per -minute: -

-
-    int n_notes = 64;
-    double tempo = 120.0;
-    double beat_duration = 60.0 / tempo;
-
- -

-The variable volume determines the amplitude of -each note, and has been chosen such that there will be no clipping -of the final output. -

-
-    float volume = 0.2;
-
- -

Composition

- -

We start with an empty coefficient set:

-
-    gaborator::coefs<float> coefs(analyzer);
-
- -

Each note is chosen randomly from the pentatonic scale and added -to the coefficient set by calling the function fill(). -The fill() function is similar to the process() -function used in previous examples, except that it can be used to -create new coefficients rather than just modifying existing ones.

- -

Each note is created by calling fill() on a region of -the time-frequency plane that covers a single band in the frequency -dimension and the duration of the note in the time dimension. Each -coefficient within this region is set to a complex number whose -magnitude decays exponentially over time, like the amplitude of a -plucked string. The phase is arbitrarily set to zero by using an -imaginary part of zero. Since notes can overlap, the new coefficients -are added to any existing ones using the += operator -rather than overwriting them.

- -

Note that band numbers increase towards lower frequencies but MIDI -note numbers increase towards higher frequencies, hence the minus sign -in front of midi_note. -

- -
-    for (int i = 0; i < n_notes; i++) {
-        int midi_note = pentatonic[rand() % 5];
-        double note_start_time = beat_duration * i;
-        double note_end_time = note_start_time + 3.0;
-        int band = analyzer.band_ref() - midi_note;
-        fill([&](int, int64_t t, std::complex<float> &coef) {
-                float amplitude =
-                    volume * expf(-2.0f * (float)(t / fs - note_start_time));
-                coef += std::complex<float>(amplitude, 0.0f);
-            },
-            band, band + 1,
-            note_start_time * fs, note_end_time * fs,
-            coefs);
-    }
-
- -

Synthesis

- -

We can now synthesize audio from the coefficients by -calling synthesize(). Audio will be generated -starting half a second before the first note to allow for the pre-ringing -of the synthesis filter, and ending a few seconds after the -last note to allow for its decay. -

-
-    double audio_start_time = -0.5;
-    double audio_end_time = beat_duration * n_notes + 5.0;
-    int64_t start_frame = audio_start_time * fs;
-    int64_t end_frame = audio_end_time * fs;
-    size_t n_frames = end_frame - start_frame;
-    std::vector<float> audio(n_frames);
-    analyzer.synthesize(coefs, start_frame, end_frame, audio.data());
-
- -

Writing the Audio

- -

Since there is no input audio file to inherit a file format from, -we need to choose a file format for the output file by filling in the -sfinfo structure:

-
-    SF_INFO sfinfo;
-    memset(&sfinfo, 0, sizeof(sfinfo));
-    sfinfo.samplerate = fs;
-    sfinfo.channels = 1;
-    sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
-
- -

The rest is identical to -Example 2: -

-
-    SNDFILE *sf_out = sf_open(argv[1], SFM_WRITE, &sfinfo);
-    if (! sf_out) {
-        std::cerr << "could not open output audio file: "
-            << sf_strerror(sf_out) << "\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;
-}
-
- -

Compiling

-

Like Example 1, this example -can be built using a one-line build command: -

-
-c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` synth.cc `pkg-config --libs sndfile` -o synth
-
-

Or using the vDSP FFT on macOS:

-
-c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` synth.cc `pkg-config --libs sndfile` -framework Accelerate -o synth
-
-

Or using PFFFT (see Example 1 for how to download and build PFFFT):

-
-c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` synth.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o synth
-
- -

Running

-

The example program can be run using the command

-
-./synth melody.wav
-
-

The resulting audio will be in melody.wav.

- - - - - diff --git a/lib/gaborator/examples/filter.cc b/lib/gaborator/examples/filter.cc deleted file mode 100644 index ad3891d..0000000 --- a/lib/gaborator/examples/filter.cc +++ /dev/null @@ -1,69 +0,0 @@ -// See ../doc/filter.html for commentary - -#include -#include -#include -#include - -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: " - << sf_strerror(sf_in) << "\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 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 analyzer(params); - std::vector band_gains(analyzer.bands_end()); - for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) { - double 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 channel(n_frames); - for (sf_count_t i = 0; i < n_frames; i++) - channel[i] = audio[i * sfinfo.channels + ch]; - gaborator::coefs coefs(analyzer); - analyzer.analyze(channel.data(), 0, channel.size(), coefs); - process([&](int band, int64_t, std::complex &coef) { - coef *= band_gains[band]; - }, - INT_MIN, INT_MAX, - INT64_MIN, INT64_MAX, - coefs); - 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 open output audio file: " - << sf_strerror(sf_out) << "\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; -} diff --git a/lib/gaborator/examples/render.cc b/lib/gaborator/examples/render.cc deleted file mode 100644 index 72cd7d3..0000000 --- a/lib/gaborator/examples/render.cc +++ /dev/null @@ -1,69 +0,0 @@ -// See ../doc/render.html for commentary - -#include -#include -#include -#include -#include -#include -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: " - << sf_strerror(sf_in) << "\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 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 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 analyzer(params); - gaborator::coefs 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 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; -} diff --git a/lib/gaborator/examples/snr.cc b/lib/gaborator/examples/snr.cc deleted file mode 100644 index b4d65d8..0000000 --- a/lib/gaborator/examples/snr.cc +++ /dev/null @@ -1,32 +0,0 @@ -// See ../doc/snr.html for commentary - -#include -#include -#include -#include -double rms(const std::vector &v) { - double sqsum = 0; - for (size_t i = 0; i < v.size(); i++) { - sqsum += v[i] * v[i]; - } - return sqrt(sqsum); -} -int main(int argc, char **argv) { - size_t len = 1000000; - std::vector signal_in(len); - std::minstd_rand rand; - std::uniform_real_distribution<> uniform(-1.0, 1.0); - for (size_t i = 0; i < len; i++) - signal_in[i] = uniform(rand); - gaborator::parameters params(48, 5e-4); - gaborator::analyzer analyzer(params); - gaborator::coefs coefs(analyzer); - analyzer.analyze(signal_in.data(), 0, len, coefs); - std::vector signal_out(len); - analyzer.synthesize(coefs, 0, len, signal_out.data()); - std::vector error(len); - for (size_t i = 0; i < len; i++) - error[i] = signal_out[i] - signal_in[i]; - double snr = rms(signal_in) / rms(error); - std::cout << std::fixed << std::setprecision(1) << 20 * log10(snr) << " dB\n"; -} diff --git a/lib/gaborator/examples/stream.cc b/lib/gaborator/examples/stream.cc deleted file mode 100644 index fb2da1b..0000000 --- a/lib/gaborator/examples/stream.cc +++ /dev/null @@ -1,72 +0,0 @@ -// See ../doc/stream.html for commentary - -#include -#include -#include -#include - -int main(int argc, char **argv) { - if (argc < 3) { - std::cerr << "usage: stream 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: " - << sf_strerror(sf_in) << "\n"; - exit(1); - } - if (sfinfo.channels != 1) { - std::cerr << "only mono files are supported\n"; - exit(1); - } - double fs = sfinfo.samplerate; - - SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo); - if (! sf_out) { - std::cerr << "could not open output audio file: " - << sf_strerror(sf_out) << "\n"; - exit(1); - } - sf_command(sf_in, SFC_SET_NORM_FLOAT, NULL, SF_FALSE); - sf_command(sf_out, SFC_SET_NORM_FLOAT, NULL, SF_FALSE); - gaborator::parameters params(12, 200.0 / fs, 440.0 / fs); - gaborator::analyzer analyzer(params); - size_t analysis_support = ceil(analyzer.analysis_support()); - size_t synthesis_support = ceil(analyzer.synthesis_support()); - std::cerr << "latency: " << analysis_support + synthesis_support << " samples\n"; - gaborator::coefs coefs(analyzer); - const size_t blocksize = 1024; - std::vector buf(blocksize); - int64_t t_in = 0; - for (;;) { - sf_count_t n_read = sf_readf_float(sf_in, buf.data(), blocksize); - if (n_read == 0) - break; - if (n_read < blocksize) - std::fill(buf.data() + n_read, buf.data() + blocksize, 0); - analyzer.analyze(buf.data(), t_in, t_in + blocksize, coefs); - process( - [&](int, int64_t, std::complex &coef) { - coef = -coef; - }, - INT_MIN, INT_MAX, - t_in - (int)analysis_support, - t_in - (int)analysis_support + (int)blocksize, - coefs); - int64_t t_out = t_in - analysis_support - synthesis_support; - analyzer.synthesize(coefs, t_out, t_out + blocksize, buf.data()); - sf_count_t n_written = sf_writef_float(sf_out, buf.data(), blocksize); - if (n_written != blocksize) { - std::cerr << "write error\n"; - exit(1); - } - forget_before(analyzer, coefs, t_out + blocksize - synthesis_support); - t_in += blocksize; - } - sf_close(sf_in); - sf_close(sf_out); - return 0; -} diff --git a/lib/gaborator/examples/synth.cc b/lib/gaborator/examples/synth.cc deleted file mode 100644 index b5aff70..0000000 --- a/lib/gaborator/examples/synth.cc +++ /dev/null @@ -1,62 +0,0 @@ -// See ../doc/synth.html for commentary - -#include -#include -#include -#include - -int main(int argc, char **argv) { - if (argc < 2) { - std::cerr << "usage: synth output.wav\n"; - exit(1); - } - double fs = 44100; - gaborator::parameters params(12, 20.0 / fs, 8.18 / fs); - gaborator::analyzer analyzer(params); - static int pentatonic[] = { 57, 60, 62, 64, 67 }; - int n_notes = 64; - double tempo = 120.0; - double beat_duration = 60.0 / tempo; - float volume = 0.2; - gaborator::coefs coefs(analyzer); - for (int i = 0; i < n_notes; i++) { - int midi_note = pentatonic[rand() % 5]; - double note_start_time = beat_duration * i; - double note_end_time = note_start_time + 3.0; - int band = analyzer.band_ref() - midi_note; - fill([&](int, int64_t t, std::complex &coef) { - float amplitude = - volume * expf(-2.0f * (float)(t / fs - note_start_time)); - coef += std::complex(amplitude, 0.0f); - }, - band, band + 1, - note_start_time * fs, note_end_time * fs, - coefs); - } - double audio_start_time = -0.5; - double audio_end_time = beat_duration * n_notes + 5.0; - int64_t start_frame = audio_start_time * fs; - int64_t end_frame = audio_end_time * fs; - size_t n_frames = end_frame - start_frame; - std::vector audio(n_frames); - analyzer.synthesize(coefs, start_frame, end_frame, audio.data()); - SF_INFO sfinfo; - memset(&sfinfo, 0, sizeof(sfinfo)); - sfinfo.samplerate = fs; - sfinfo.channels = 1; - sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; - SNDFILE *sf_out = sf_open(argv[1], SFM_WRITE, &sfinfo); - if (! sf_out) { - std::cerr << "could not open output audio file: " - << sf_strerror(sf_out) << "\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; -} diff --git a/lib/gaborator/gaborator/affine_transform.h b/lib/gaborator/gaborator/affine_transform.h deleted file mode 100644 index c2ea997..0000000 --- a/lib/gaborator/gaborator/affine_transform.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// A class for affine transforms (ax + b) of scalar values -// -// Copyright (C) 2020-2021 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_AFFINE_TRANSFORM_H -#define _GABORATOR_AFFINE_TRANSFORM_H - -namespace gaborator { - -struct affine_transform { - affine_transform(): a(0), b(0) { } - affine_transform(double a_, double b_): a(a_), b(b_) { } - affine_transform(const affine_transform &rhs): a(rhs.a), b(rhs.b) { } - double operator()(double x) const { return a * x + b; } - affine_transform inverse() const { - return affine_transform(1.0 / a, -b / a); - } - static affine_transform identity() { return affine_transform(1, 0); } - double a, b; -}; - -// Composition - -static inline affine_transform -operator *(const affine_transform &a, const affine_transform &b) { - return affine_transform(a.a * b.a, a.a * b.b + a.b); -} - -// Equality - -static inline bool -operator ==(const affine_transform &a, const affine_transform &b) { - return a.a == b.a && a.b == b.b; -} - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/fft.h b/lib/gaborator/gaborator/fft.h deleted file mode 100644 index 66921e9..0000000 --- a/lib/gaborator/gaborator/fft.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Fast Fourier transform -// -// Copyright (C) 2016-2021 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" -#define GABORATOR_USE_REAL_FFT 1 -#define GABORATOR_MIN_FFT_SIZE 1 -#elif GABORATOR_USE_PFFFT -#include "gaborator/fft_pffft.h" -#define GABORATOR_USE_REAL_FFT 1 -#define GABORATOR_MIN_FFT_SIZE 32 -#else -// Use the naive FFT -// Do not define GABORATOR_USE_REAL_FFT as it is slower than -// using the complex code. -#define GABORATOR_MIN_FFT_SIZE 1 -#endif - -#endif diff --git a/lib/gaborator/gaborator/fft_naive.h b/lib/gaborator/gaborator/fft_naive.h deleted file mode 100644 index 875d99e..0000000 --- a/lib/gaborator/gaborator/fft_naive.h +++ /dev/null @@ -1,191 +0,0 @@ -// -// Fast Fourier transform, naive reference implementations -// -// Copyright (C) 1992-2021 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 -#include -#include -#include - -#include - -namespace gaborator { - -template -struct fft { - typedef typename std::iterator_traits::value_type C; // complex - typedef typename C::value_type T; // float/double - typedef typename std::vector 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() { - size_t wt_size = wtab.size(); - for (size_t 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; -}; - -// 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 -struct rfft { - typedef typename std::iterator_traits::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 cf; -}; - -} // Namespace - -#endif diff --git a/lib/gaborator/gaborator/fft_pffft.h b/lib/gaborator/gaborator/fft_pffft.h deleted file mode 100644 index b3e64b9..0000000 --- a/lib/gaborator/gaborator/fft_pffft.h +++ /dev/null @@ -1,214 +0,0 @@ -// -// Fast Fourier transform using PFFFT -// -// Copyright (C) 2017-2021 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 -#include - -#include -#include - - -#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 *> { - typedef std::complex *I; - typedef const std::complex *CONST_I; - typedef std::iterator_traits::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>::iterator - -template <> -struct fft>::iterator>: - public fft *> -{ - typedef fft *> base; - typedef std::vector>::iterator I; - fft(unsigned int n_): fft *>(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 *> { - typedef std::complex *I; - typedef const std::complex *CONST_I; - typedef std::iterator_traits::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 wsave; -}; - -// Real FFT - -template <> -struct rfft *> { - typedef std::complex *CI; // Complex iterator - typedef const std::complex *CONST_CI; - typedef typename std::iterator_traits::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(in)[0] = C(tmp.real(), in[n >> 1].real()); - pffft_transform_ordered(setup, (const float *) in, out, NULL, PFFFT_BACKWARD); - const_cast(in)[0] = tmp; - } - -private: - // Size of the transform - unsigned int n; - PFFFT_Setup *setup; -}; - -#undef GABORATOR_PFFFT_CHECK_ALIGN - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/fft_vdsp.h b/lib/gaborator/gaborator/fft_vdsp.h deleted file mode 100644 index f0fe232..0000000 --- a/lib/gaborator/gaborator/fft_vdsp.h +++ /dev/null @@ -1,209 +0,0 @@ -// -// Fast Fourier transform using the Apple vDSP framework -// -// Copyright (C) 2013-2021 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 - -#include -#include - -#include - -#include -#include -#include -#include - -#include - -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 *> { - typedef std::complex *I; - typedef typename std::iterator_traits::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; -}; - -// Real FFT - -template <> -struct rfft *> { - typedef std::complex *CI; // Complex iterator - typedef const std::complex *CONST_CI; - typedef typename std::iterator_traits::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_), log2n(log2_int_exact(n)) { - setup = vDSP_create_fftsetup(log2n, kFFTRadix2); - } - ~rfft() { - vDSP_destroy_fftsetup(setup); - } - - unsigned int size() { return n; } - - // out-of-place only - void - transform(CONST_RI in, CI 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_zrop(setup, - &si, 2, - &so, 2, - log2n, kFFTDirection_Forward); - // Undo vDSP scaling - for (unsigned int i = 0; i < (n >> 1); i++) - out[i] *= (T)0.5; - 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); - } - - void - itransform(CONST_CI in, RI out) { - C tmp = in[0]; - const_cast(in)[0] = C(tmp.real(), in[n >> 1].real()); - DSPSplitComplex si; - si.realp = (float *) in; - si.imagp = (float *) in + 1; - DSPSplitComplex so; - so.realp = (float *) out; - so.imagp = (float *) out + 1; - vDSP_fft_zrop(setup, - &si, 2, - &so, 2, - log2n, kFFTDirection_Inverse); - const_cast(in)[0] = tmp; - } - -private: - // Size of the transform - unsigned int n; - unsigned int log2n; - FFTSetup setup; -}; - -// Support transforming std::vector>::iterator - -template <> -struct fft>::iterator>: - public fft *> -{ - typedef fft *> base; - typedef typename std::vector>::iterator I; - fft(unsigned int n_): fft *>(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 diff --git a/lib/gaborator/gaborator/gaborator.h b/lib/gaborator/gaborator/gaborator.h deleted file mode 100644 index 358f2e2..0000000 --- a/lib/gaborator/gaborator/gaborator.h +++ /dev/null @@ -1,2966 +0,0 @@ -// -// Constant Q spectrum analysis and resynthesis -// -// Copyright (C) 2015-2021 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_GABORATOR_H -#define _GABORATOR_GABORATOR_H - -#define __STDC_LIMIT_MACROS - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "gaborator/fft.h" -#include "gaborator/gaussian.h" -#include "gaborator/affine_transform.h" -#include "gaborator/pod_vector.h" -#include "gaborator/pool.h" -#include "gaborator/ref.h" -#include "gaborator/vector_math.h" - - -namespace gaborator { - -using std::complex; - -// An integer identifying an audio sample -typedef int64_t sample_index_t; - -// An integer identifying a coefficient -typedef int64_t coef_index_t; - -// An integer identifying a slice -typedef int64_t slice_index_t; - -// See https://tauday.com/tau-manifesto -static const double tau = 2.0 * M_PI; - -// Round up to next higher or equal power of 2 - -inline int -next_power_of_two(int x) { - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - return x + 1; -} - -// Determine if x is a power of two. -// Note that this considers 0 to be a power of two. - -static inline bool -is_power_of_two(unsigned int x) { - return (x & (x - 1)) == 0; -} - -// Given a power of two v, determine log2(v) -// https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 - -static inline unsigned int whichp2(unsigned int v) { - assert(is_power_of_two(v)); - unsigned int r = (v & 0xAAAAAAAA) != 0; - r |= ((v & 0xCCCCCCCC) != 0) << 1; - r |= ((v & 0xF0F0F0F0) != 0) << 2; - r |= ((v & 0xFF00FF00) != 0) << 3; - r |= ((v & 0xFFFF0000) != 0) << 4; - return r; -} - -// Floor division: return the integer part of a / b -// rounded down (not towards zero). For positive b only. - -inline int64_t floor_div(int64_t a, int64_t b) { - assert(b > 0); - if (a >= 0) - return a / b; - else - return (a - b + 1) / b; -} - -// Floating point modulus, the remainder r of a / b -// satisfying 0 <= r < b even for negative a. -// For positive b only. - -static inline double -sane_fmod(double a, double b) { - assert(b > 0); - double m = fmod(a, b); - if (m < 0) - m += b; - return m; -} - -// Do an arithmetic left shift of a 64-bit signed integer. This is -// what a << b ought to do, but according to the C++11 draft (n3337), -// section 5.8, that invokes undefined behavior when a is negative. -// GCC is actually smart enough to optimize this into a single shlq -// instruction. -// -// No corresponding kludge is needed for right shifts, because a right -// shift of a negative signed integer is implementation-defined, not -// undefined, and we trust implementations to define it sanely. - -static inline int64_t -shift_left(int64_t a, unsigned int b) { - if (a < 0) - return -(((uint64_t) -a) << b); - else - return (((uint64_t) a) << b); -} - -// Convert between complex types - -template -complex c2c(complex c) { return complex(c.real(), c.imag()); } - -// Convert a sequence of complex values to real - -template -O complex2real(I b, I e, O o) { - while (b != e) { - *o++ = (*b++).real(); - } - return o; -} - -// A vector-like object that allows arbitrary integer indices -// (positive or negative, but excluding the largest possible integer) -// and automatically resizes the storage. Uses storage proportional -// to the difference between the smallest and largest index value (for -// example, if indices range from -102 to -100 (inclusive), memory use -// is on the order of 3 elements). -// -// T is the element type -// I is the integer index type - -template -struct range_vector { - range_vector() { - init_bounds(); - } - range_vector(const range_vector &) = default; - range_vector &operator=(const range_vector &rhs) = default; - range_vector(range_vector &&rhs): - v(std::move(rhs.v)), - lower(rhs.lower), - upper(rhs.upper) - { - rhs.init_bounds(); - } - range_vector &operator=(range_vector &&rhs) { - if (this == &rhs) - return *this; - v = std::move(rhs.v); - lower = rhs.lower; - upper = rhs.upper; - rhs.init_bounds(); - return *this; - } -private: - void init_bounds() { - lower = std::numeric_limits::max(); - upper = std::numeric_limits::min(); - } - T *unchecked_get(I i) { - return &v[(size_t)(i & ((I)v.size() - 1))]; - } - const T *unchecked_get(I i) const { - return &v[i & ((I)v.size() - 1)]; - } - -public: - // Get a pointer to an existing element, or null if out of range - const T * - get(I i) const { - if (! has_index(i)) - return 0; - return unchecked_get(i); - } - - // Note: Reference returned becomes invalid when range_vector - // is changed - T & - get_or_create(I i) { - if (! has_index(i)) - extend(i); - return *unchecked_get(i); - - } - - // Get a reference to the element at index i, which must be valid - T & - get_existing(I i) { - assert(has_index(i)); - return *unchecked_get(i); - } - - // Const version of the above - const T & - get_existing(I i) const { - assert(has_index(i)); - return *unchecked_get(i); - } - -private: - void extend(I i) { - I new_lower = lower; - I new_upper = upper; - if (i < lower) - new_lower = i; - if (i + 1 > upper) - new_upper = i + 1; - I old_size = v.size(); - I new_need = new_upper - new_lower; - if (new_need > old_size) { - if (old_size == 0) { - v.resize(1); - } else { - I new_size = old_size; - while (new_size < new_need) - new_size *= 2; - v.resize(new_size); - if (old_size) { - for (I j = lower; j < upper; j++) { - I jo = j & (old_size - 1); - I jn = j & (new_size - 1); - if (jo != jn) - std::swap(v[jo], v[jn]); - } - } - } - } - lower = new_lower; - upper = new_upper; - } - -public: - // Erase the elements whose index is less than "limit" - void erase_before(I limit) { - I i = lower; - for (; i < upper && i < limit; i++) - *unchecked_get(i) = T(); - lower = i; - } - - void clear() { - v.clear(); - init_bounds(); - } - - I begin_index() const { return lower; } - I end_index() const { return upper; } - bool empty() const { return lower >= upper; } - bool has_index(I i) const { return i >= lower && i < upper; } - -private: - std::vector v; - I lower, upper; -}; - -// Calculate the size of the alias-free part (the "filet") -// of a signal slice of size "fftsize" - -static inline unsigned int filet_part(unsigned int fftsize) { - return fftsize >> 1; -} - -// Calculate the size of the padding (the "fat") at each -// end of a signal slice of size "fftsize" - -static inline unsigned fat_part(unsigned int fftsize) { - return fftsize >> 2; -} - -// Per-band, per-plan data - -template -struct band_plan { - typedef complex C; - unsigned int sftsize; // Size of "short FFT" spanning the band - unsigned int sftsize_log2; // log2(sftsize) - fft *sft; // Fourier transform for windows, of size sftsize - std::vector kernel; // Frequency-domain filter kernel - std::vector dual_kernel; // Dual of the above - pod_vector shift_kernel; // Complex exponential for fractional frequency compensation - pod_vector shift_kernel_conj; // Conjugate of the above - int fq_offset_int; // Frequency offset in bins (big-FFT bin of left window edge) - double center; // Center frequency in units of FFT bins - int icenter; // Center frequency rounded to nearest integer FFT bin -}; - -// Frequency band parameters shared between octaves - -template -struct band_params: public refcounted { - typedef complex C; - bool dc; // True iff this is the lowpass (DC) band - double ff; // Center (bp) or corner (lp) frequency in units of the sampling frequency - double ffsd; // Standard deviation of the bandpass Gaussian, as fractional frequency - unsigned int step; // Signal samples per coefficient sample - unsigned int step_log2; // log2(step) - double ff_support; // Filter support in frequency domain - double time_support; // Filter support in time domain, in octave subsamples - std::vector> anl_plans; - std::vector> syn_plans; -}; - -// Downsampling parameters. These have some similarity to band -// parameters, but only some. For example, these may use a real -// rather than complex FFT for the "short FFT". - -template -struct downsampling_params { - typedef complex C; - unsigned int sftsize; - std::vector kernel; // Frequency-domain filter kernel - std::vector dual_kernel; -#if GABORATOR_USE_REAL_FFT - rfft *rsft; -#else - fft *sft; -#endif -}; - -// Forward declarations -template struct analyzer; -template struct zone; -template> struct coefs; -template struct sliced_coefs; -template *, class C = complex> - struct row_source; -template *, class C = complex> - struct row_dest; -template *, class C = complex> - struct row_add_dest; - -// Abstract class for tracking changes to a coefficient set. -// This may be used for updating a resolution pyramid of -// magnitude data. - -template -struct shadow { - virtual ~shadow() { } - virtual void update(const coefs> &msc, - sample_index_t i0, sample_index_t i1) = 0; - // Deprecated - virtual void update(int oct, int ze, const sliced_coefs> &sc, - slice_index_t sli0, slice_index_t sli1) = 0; -}; - -// Per-band coefficient metadata, shared between octaves. - -struct band_coefs_meta { - unsigned int slice_len; // Number of coefficients per slice - unsigned short slice_len_log2; // Log2 of the above - // Log2 of the downsampling factor of the coefficients in - // this band relative to the signal samples. The value - // applies as such in the top octave; in other octaves, - // the octave number needs to be added. - unsigned short step_log2; - // Offset of the beginning of this band in the data array - unsigned int band_offset; -}; - -// Per-zone coefficient metadata. This contains information -// describing an "oct_coefs" structure that is common to many -// instances and should not be duplicated in each one. - -struct zone_coefs_meta { - typedef std::vector band_vector; - void init(const band_vector &bands_) { - bands = bands_; - unsigned int offset = 0; - for (band_coefs_meta &b: bands) { - b.band_offset = offset; - offset += b.slice_len; - } - total_size = offset; - } - band_vector bands; - // Total size of data array (in elements, not bytes) - unsigned int total_size; -}; - -// Per-octave coefficient metadata. -// Cf. struct octave - -struct oct_coefs_meta { - zone_coefs_meta *z; - unsigned int n_bands_above; // Total number of bands in higher octaves -}; - -// Coefficient metadata for multirate coefficients. - -struct coefs_meta: public refcounted { - coefs_meta() = default; - coefs_meta(const coefs_meta &) = delete; - unsigned int n_octaves; - unsigned int n_bands_total; - unsigned int bands_per_octave; - unsigned int slice_len; // octave subsamples per slice - std::vector zones; - std::vector octaves; -}; - -// Split a "global band number" gbno into an octave and band -// number within octave ("obno"). -// -// Global band numbers start at 0 for the band at or close to -// fs/2, and increase towards lower frequencies. -// -// Include the DC band if "dc" is true. -// Returns true iff gbno is valid. - -static inline bool -bno_split(const coefs_meta &meta, int gbno, int &oct, unsigned int &obno, bool dc) { - if (gbno < 0) { - // Above top octave - return false; - } else if (gbno >= (int)meta.n_bands_total - 1) { - // At or below DC - if (gbno == (int)meta.n_bands_total - 1) { - // At DC - if (dc) { - oct = meta.n_octaves - 1; - obno = 0; - return true; - } else { - return false; - } - } else { - // Below DC - return false; - } - } else { - // Within bandpass region - // Start by determining the octave - int n_bands_top_octave = (int)meta.octaves[0].z->bands.size(); - if (gbno < n_bands_top_octave) { - // Top octave - oct = 0; - obno = n_bands_top_octave - 1 - gbno; - return true; - } - gbno -= n_bands_top_octave; - int oct_tmp = 1 + gbno / meta.bands_per_octave; - int obno_tmp = gbno % meta.bands_per_octave; - oct = oct_tmp; - // Now determine the band within the octave. - // obno_tmp counts down, but obno counts up. - obno = (unsigned int)meta.octaves[oct_tmp].z->bands.size() - 1 - obno_tmp; - return true; - } -} - -// The inverse of bno_split(). Returns a gbno. The arguments must -// be valid. - -static inline -int bno_merge(const coefs_meta &meta, int oct, unsigned int obno) { - unsigned int n_bands = (unsigned int)meta.octaves[oct].z->bands.size(); - assert(obno < n_bands); - int bno_from_end = n_bands - 1 - obno; - return bno_from_end + meta.octaves[oct].n_bands_above; -} - -// Coefficients of a single octave for a single input signal slice. -// C is the coefficient type, typically complex but can also -// be e.g. unsigned int to store cluster numbers, or float to store -// magnitudes. - -template -struct oct_coefs: public refcounted { - oct_coefs(const zone_coefs_meta &zmeta_, bool clear_ = true): - zmeta(zmeta_), - data(zmeta.total_size), - bands(*this) - { - if (clear_) - clear(); - } - oct_coefs(const oct_coefs &) = delete; - uint64_t estimate_memory_usage() const { - return zmeta.total_size * sizeof(C) + sizeof(*this); - } - void clear() { - std::fill(data.begin(), data.end(), C()); - } - // Deep copy - oct_coefs &operator=(const oct_coefs &rhs) { - assert(data.size() == rhs.data.size()); - memcpy(data.data(), rhs.data.data(), data.size() * sizeof(C)); - return *this; - } - - const zone_coefs_meta &zmeta; - - // The data for all the bands are allocated together - // as a single vector to reduce the number of allocations - pod_vector data; - // Vector-like collection of pointers into "data", one for each band - struct band_array { - band_array(oct_coefs &outer_): outer(outer_) { } - C *operator[](size_t i) const { - return outer.data.data() + outer.zmeta.bands[i].band_offset; - } - size_t size() const { return outer.zmeta.bands.size(); } - oct_coefs &outer; - } bands; -}; - -// Add the oct_coefs "b" to the oct_coefs "a" - -template -void add(oct_coefs &a, const oct_coefs &b) { - size_t n_bands = a.bands.size(); - assert(n_bands == b.bands.size()); - for (size_t obno = 0; obno < n_bands; obno++) { - unsigned int len = a.zmeta.bands[obno].slice_len; - C *band_a = a.bands[obno]; - C *band_b = b.bands[obno]; - for (unsigned int j = 0; j < len; j++) { - band_a[j] += band_b[j]; - } - } -} - -// Sliced coefficients. These cover an arbitrary time range, but only -// a single octave. Template argument is as for struct oct_coefs. -// This is default constructible so that we can create an array of -// them, but not usable until "meta" has been set up. - -template -struct sliced_coefs { - typedef range_vector>, slice_index_t> slices_t; - uint64_t estimate_memory_usage() const { - unsigned int n = 0; - size_t size_each = 0; - for (slice_index_t sl = slices.begin_index(); sl < slices.end_index(); sl++) { - const ref> &t = slices.get_existing(sl); - if (t) { - if (! size_each) - size_each = (size_t)t->estimate_memory_usage(); - n++; - } - } - return n * size_each; - } - void clear() { - slices.clear(); - } - zone_coefs_meta *meta; - slices_t slices; -}; - -// Get a pointer to an existing existing coefficient slice, -// or null if one does not exist. Like get_or_create_coefs(), -// this hides the distinction between the two types of nonexistence. - -template -oct_coefs *get_existing_coefs(const sliced_coefs &sc, - slice_index_t i) -{ - const ref> *p = sc.slices.get(i); - if (! p) - return 0; - return p->get(); -} - -// Get an existing coefficient slice, or create a new one. Note that -// this hides the distinction between two types of nonexistence: that -// of slices outside the range of the range_vector, and that of -// missing slices within the range (having a null ref). CT is the -// coefficient type, which is typically C aka complex, but can be -// different, for example float to represent magnitudes. - -template -oct_coefs &get_or_create_coefs(sliced_coefs &sc, slice_index_t i) { - ref> &p(sc.slices.get_or_create(i)); - if (! p) - p.reset(new oct_coefs(*sc.meta)); - return *p; -} - -// Return the signal sample time corresponding to coefficient sample 0 -// of coefficient slice 0, for slices of length len. It would be nice -// if this were zero, but for historical reasons, it's offset by half -// a slice (corresponding to the analysis fat). - -static inline int coef_offset(int len) { - return len >> 1; -} - -// Get the base 2 logarithm of the downsampling factor of -// band "obno" in octave "oct" - -static inline int -band_scale_exp(const zone_coefs_meta &meta, int oct, unsigned int obno) { - return meta.bands[obno].step_log2 + oct; -} - -// Return the coefficient index (the time in terms of coefficient -// subsamples) of the first cofficient of slice "sli" of band -// "obno" in octave "oct" - -static inline coef_index_t -coef_time(const zone_coefs_meta &meta, slice_index_t sli, int oct, int obno) { - int len = meta.bands[obno].slice_len; - return coef_offset(len) + sli * len; -} - -// Return the sample index (the time in terms of samples) time of -// coefficient "i" in slice "sli" of band "obno" in octave "oct" - -static inline sample_index_t -sample_time(const zone_coefs_meta &meta, slice_index_t sli, int i, int oct, int obno) { - coef_index_t sst = coef_time(meta, sli, oct, obno) + i; - return shift_left(sst, band_scale_exp(meta, oct, obno)); -} - -// Multirate sliced coefficients. These cover an arbitrary time -// range and the full frequency range (all octaves). -// Template arguments: -// T analyzer sample data type -// C coefficient data type -// Note default for template argument C defined in forward declaration. - -template -struct coefs { - typedef C value_type; - coefs(const analyzer &anl_, shadow *shadow_ = 0): - octaves(anl_.n_octaves), shadow0(shadow_) - { - meta = anl_.cmeta_any.get(); - // Set up shortcut pointer to zone metadata in each octave - for (unsigned int oct = 0; oct < octaves.size(); oct++) - octaves[oct].meta = meta->octaves[oct].z; - } - uint64_t estimate_memory_usage() const { - uint64_t s = 0; - for (unsigned int oct = 0; oct < octaves.size(); oct++) - s += octaves[oct].estimate_memory_usage(); - return s; - } - void clear() { - for (unsigned int oct = 0; oct < octaves.size(); oct++) - octaves[oct].clear(); - } - coefs_meta *meta; - std::vector> octaves; - shadow *shadow0; -}; - -// Read coefficients i0..i1 of band gbno in msc into buf. - -template -void read(const coefs &msc, int gbno, - coef_index_t i0, coef_index_t i1, C *buf) -{ - int oct; - unsigned int obno; // Band number within octave - bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); - assert(valid); - row_source(msc, oct, obno)(i0, i1, buf); -} - -template -void write(coefs &msc, int gbno, - coef_index_t i0, coef_index_t i1, C *buf) -{ - int oct; - unsigned int obno; // Band number within octave - bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); - assert(valid); - row_dest(msc, oct, obno)(i0, i1, buf); -} - -template -void add(coefs &msc, int gbno, - coef_index_t i0, coef_index_t i1, C *buf) -{ - int oct; - unsigned int obno; // Band number within octave - bool valid = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); - assert(valid); - row_add_dest(msc, oct, obno)(i0, i1, buf); -} - -// Return the base 2 logarithm of the time step (aka downsampling -// factor) of band "gbno". - -static inline -unsigned int band_step_log2(const coefs_meta &meta, int gbno) { - int oct; - unsigned int obno; - bool valid = bno_split(meta, gbno, oct, obno, true); - assert(valid); - return band_scale_exp(*meta.octaves[oct].z, oct, obno); -} - - -// Convert a signal time t into a coefficient sample -// index. t must coincide with a coefficient sample time. - -static inline -coef_index_t t2i_exact(const coefs_meta &meta, int gbno, sample_index_t t) { - int shift = band_step_log2(meta, gbno); - int64_t mask = ((sample_index_t)1 << shift) - 1; - assert((t & mask) == 0); - return t >> shift; -} - -// Read a single coefficient sample at signal time t, -// which must coincide with a coefficient sample time - -template -C read1t(const coefs &msc, int gbno, sample_index_t t) { - coef_index_t i = t2i_exact(*msc.meta, gbno, t); - C c; - read(msc, gbno, i, i + 1, &c); - return c; -} - -// Read a single coefficient sample at signal time t, -// which must coincide with a coefficient sample time - -template -void write1t(coefs &msc, int gbno, sample_index_t t, C c) { - coef_index_t i = t2i_exact(*msc.meta, gbno, t); - write(msc, gbno, i, i + 1, &c); -} - -// Perform an fftshift of the range between iterators a and b. -// Not optimized - not for use in inner loops. - -template -void fftshift(I b, I e) { - size_t len = e - b; - assert(len % 2 == 0); - for (size_t i = 0; i < len / 2; i++) - std::swap(*(b + i), *(b + len / 2 + i)); -} - -// Given an unsigned index i into an FFT of a power-of-two size -// "size", return the corresponding signed index, ranging from -size/2 -// to size/2-1. This is equivalent to sign extension of an integer of -// log2(size) bits; see Hacker's Delight, page 18. This can be used -// to convert an FFT index into a frequency (positive or negative; -// note that Nyquist is considered negatitive = -fs/2). - -static inline int signed_index(unsigned int i, unsigned int size) { - unsigned int t = size >> 1; - return (i ^ t) - t; -} - -// Evaluate a Gaussian windowed lowpass filter frequency response. -// This is the convolution of a rectangle centered at f=0 and a Gaussian, -// and corresponds to a Gaussian windowed sinc in the time domain. -// The -6 dB cutoff freqency is ff_cutoff (a fractional frequency), -// the standard deviation of the Gaussian is ff_sd, and the frequency -// response is evaluated at ff. The frequency response is smooth at -// f=0 even if the transition bands overlap. - -inline double -gaussian_windowed_lowpass_1(double ff_cutoff, double ff_sd, double ff) { - return - // A rectangle is the sum of a rising step and a later falling - // step, or the difference between a rising step and a later - // rising step. By linearity, a Gaussian filtered rectangle - // is the difference between two Gaussian filtered rising - // steps. - gaussian_edge(ff_sd, -ff + ff_cutoff) - - gaussian_edge(ff_sd, -ff - ff_cutoff); -} - -// Fill a sequence with a frequency-ddomain lowpass filter as above. -// The returned filter covers the full frequency range from 0 to fs -// (with negative frequencies at the end, the usual convention for FFT -// spectra). -// -// When center=true, construct a time-domain window instead, -// passing the center of the time-domain signal. -// -// The result is stored between iterators b and e, which must have a -// real value_type. - -template -inline void gaussian_windowed_lowpass(double ff_cutoff, double ff_sd, - I b, I e, bool center = false) -{ - size_t len = e - b; - double inv_len = 1.0 / len; - for (I it = b; it != e; ++it) { - size_t i = it - b; - double thisff; - if (center) - // Symmetric around center - thisff = std::abs(i - (len * 0.5)) * inv_len; - else - // Symmetric around zero - thisff = (i > len / 2 ? len - i : i) * inv_len; - *it = gaussian_windowed_lowpass_1(ff_cutoff, ff_sd, thisff); - } -} - -// A set of octaves having identical parameters form a "zone", -// and their shared parameters are stored in a "struct zone". - -template -struct zone: public refcounted { - zone(): n_bands(0) { } - ~zone() { } - // Zone number, 0..3 - unsigned int zno; - // Total number of bands, including DC band if lowest octave - unsigned int n_bands; - unsigned int max_step_log2; - // Band parameters by increasing frequency; DC band is index 0 if - // present - std::vector>> bandparams; - std::vector>> mock_bandparams; -}; - -template -struct octave { - zone *z; -}; - -// Helper function for pushing parameters onto the vectors in struct zone - -template -void push(std::vector>> &v, band_params *p) { - v.push_back(ref>(p)); -} - -// Phase conventions: coef_phase::absolute means the phase of a -// coefficient at time tc is relative to e^(i tau f t), and -// coef_phase::local means it is relative to -// e^(i tau f (t - tc)) - -enum class coef_phase { global, local }; - -// A set of spectrum analysis parameters - -struct parameters { - parameters(unsigned int bands_per_octave_, - double ff_min_, - double ff_ref_ = 1.0, - double overlap_ = 0.7, - double max_error_ = 1e-5): - bands_per_octave(bands_per_octave_), - ff_min(ff_min_), - ff_ref(ff_ref_), - overlap(overlap_), - max_error(max_error_), - coef_scale(1.0), - synthesis(true), - multirate(true) - { - init_v1(); - } - // Pseudo-constructor with version 1 defaults - static parameters v1(unsigned int bands_per_octave_, - double ff_min_, - double ff_ref_ = 1.0, - double overlap_ = 0.7, - double max_error_ = 1e-5) - { - parameters p(bands_per_octave_, ff_min_, ff_ref_, overlap_, max_error_); - p.init_v1(); - return p; - } - // Pseudo-constructor with version 2 defaults - static parameters v2(unsigned int bands_per_octave_, - double ff_min_, - double ff_ref_ = 1.0, - double overlap_ = 0.7, - double max_error_ = 1e-5) - { - parameters p(bands_per_octave_, ff_min_, ff_ref_, overlap_, max_error_); - p.init_v2(); - return p; - } - void init_v1() { - phase = coef_phase::global; - bandwidth_version = 1; - lowpass_version = 1; - } - void init_v2() { - phase = coef_phase::local; - bandwidth_version = 2; - lowpass_version = 2; - } - // Provide an operator< so that we can create a set or map of parameters - bool operator<(const parameters &b) const { -#define GABORATOR_COMPARE_LESS(member) do { \ - if (member < b.member) \ - return true; \ - if (member > b.member) \ - return false; \ - } while(0) - GABORATOR_COMPARE_LESS(bands_per_octave); - GABORATOR_COMPARE_LESS(ff_min); - GABORATOR_COMPARE_LESS(ff_ref); - GABORATOR_COMPARE_LESS(overlap); - GABORATOR_COMPARE_LESS(max_error); - GABORATOR_COMPARE_LESS(phase); - GABORATOR_COMPARE_LESS(bandwidth_version); - GABORATOR_COMPARE_LESS(lowpass_version); - GABORATOR_COMPARE_LESS(coef_scale); - GABORATOR_COMPARE_LESS(synthesis); - GABORATOR_COMPARE_LESS(multirate); -#undef GABORATOR_COMPARE_LESS - // Equal - return false; - } - bool operator==(const parameters &b) const { - return !((*this < b) || (b < *this)); - } - // The frequency increases by a factor of band_spacing from - // one bandpass band to the next. - double band_spacing_log2() const { - return 1.0 / bands_per_octave; - } - double band_spacing() const { - return exp2(band_spacing_log2()); - } - // The standard deviation of the Gaussian in units of the mean - double sd() const { - return overlap * - (bandwidth_version == 1 ? - band_spacing() - 1 : - log(2) / bands_per_octave); - } - - // Defining Q as the frequency divided by the half-power bandwidth, - // we get - // - // norm_gaussian(sd, hbw) = sqrt(2) - // - // (%i1) e1: exp(-(hbw * hbw) / (2 * sd * sd)) = 1 / sqrt(2); - // (%i2) solve(e1, hbw); - // (%o2) [hbw = - sqrt(log(2)) sd, hbw = sqrt(log(2)) sd] - double q() const { - return 1.0 / (2 * sqrt(log(2)) * sd()); - } - - template friend class analyzer; - unsigned int bands_per_octave; - double ff_min; - double ff_ref; - double overlap; - double max_error; - coef_phase phase; - int bandwidth_version; - int lowpass_version; - double coef_scale; - bool synthesis; // Synthesis is supported - bool multirate; -}; - -// Like std::fill, but returns the end iterator - -template -I fill(I b, I e, T v) { - std::fill(b, e, v); - return e; -} - -// Multiply a vector by a scalar, in-place. -// Used only at the setup stage, so performance is not critical. - -template -void scale_vector(V &v, S s) { - for (auto &e: v) - e *= s; -} - -// Zero-padding source wrapper. This returns data from the underlying -// source within the interval src_i0 to src_i1, and zero elsewhere. - -template -struct zeropad_source { - typedef typename std::iterator_traits::value_type T; - zeropad_source(const S &source_, int64_t src_i0_, int64_t src_i1_): - source(source_), src_i0(src_i0_), src_i1(src_i1_) - { } - OI operator()(int64_t i0, int64_t i1, OI output) const { - int64_t overlap_begin = std::max(i0, src_i0); - int64_t overlap_end = std::min(i1, src_i1); - if (overlap_end <= overlap_begin) { - // No overlap - output = gaborator::fill(output, output + (i1 - i0), (T)0); - } else { - // Some overlap - if (overlap_begin != i0) { - output = gaborator::fill(output, output + (overlap_begin - i0), (T)0); - } - output = source(overlap_begin, overlap_end, output); - if (overlap_end != i1) { - output = gaborator::fill(output, output + (i1 - overlap_end), (T)0); - } - } - return output; - } - const S &source; - int64_t src_i0, src_i1; -}; - -template -struct pointer_source { - pointer_source(const T *p_, int64_t buf_i0_, int64_t buf_i1_): - p(p_), buf_i0(buf_i0_), buf_i1(buf_i1_) { } - T *operator()(int64_t i0, int64_t i1, T *output) const { - assert(i1 >= i0); - assert(i0 >= buf_i0); - assert(i1 <= buf_i1); - return std::copy(p + (i0 - buf_i0), p + (i1 - buf_i0), output); - } - const T *p; - int64_t buf_i0, buf_i1; -}; - -// Fill the buffer at dst, of length dstlen, with data from src where -// available, otherwise with zeroes. The data in src covers dst indices -// from i0 (inclusive) to i1 (exclusive). - -template -void copy_overlapping_zerofill(T *dst, size_t dstlen, const T *src, - int64_t i0, int64_t i1) -{ - pointer_source ps(src, i0, i1); - zeropad_source, T *> zs(ps, i0, i1); - zs(0, dstlen, dst); -} - -// Given a set of FFT coefficients "coefs" of a real sequence, where -// only positive-frequency coefficients (including DC and Nyquist) are -// valid, return the coefficient for an arbitrary frequency index "i" -// which may correspond to a negative frequency, or even an alias -// outside the range (0..fftsize-1). - -template -complex get_real_spectrum_coef(complex *coefs, int i, unsigned int fftsize) { - i &= fftsize - 1; - // Note that this is >, not >=, becase fs/2 is considered nonnegative - bool neg_fq = (i > (int)(fftsize >> 1)); - if (neg_fq) { - i = fftsize - i; - } - complex c = coefs[i]; - if (neg_fq) { - c = conj(c); - } - return c; -} - -// A set of buffers of various sizes used for temporary vectors during -// analysis. These are allocated as a single block to reduce the -// number of dynamic memory allocations. - -template -struct buffers { - static const size_t maxbufs = 10; - typedef complex C; - buffers(unsigned int fftsize_max, - unsigned int sftsize_max): - n(0) - { - offset[0] = 0; - // Define the size of each buffer - def(fftsize_max * sizeof(C)); // 0 - def(fftsize_max * sizeof(C)); // 1 - def(sftsize_max * sizeof(C)); // 2 - def(sftsize_max * sizeof(C)); // 3 - def(sftsize_max * sizeof(C)); // 4 - def(fftsize_max * sizeof(T)); // 5 - assert(n <= maxbufs); - data = ::operator new(offset[n]); - } - ~buffers() { - ::operator delete(data); - } - void def(size_t size) { - size_t o = offset[n++]; - offset[n] = o + size; - } - // A single buffer of element type E - template - struct buffer { - typedef E *iterator; - buffer(void *b_, void *e_): - b((E *)b_), e((E *)e_) - { } - iterator begin() const { return b; } - iterator end() const { return e; } - E *data() { return b; } - const E *data() const { return b; } - E &operator[](size_t i) { return b[i]; } - const E &operator[](size_t i) const { return b[i]; } - size_t size() const { return e - b; } - private: - E *b; - E *e; - }; - // Get buffer number "i" as a vector-like object with element type "E" - // and a length of "len" elements. - template - buffer get(size_t i, size_t len) { - len *= sizeof(E); - size_t o = offset[i]; - assert(len <= offset[i + 1] - o); - return buffer((char *)data + o, (char *)data + o + len); - } -private: - void *data; - size_t n; - size_t offset[maxbufs + 1]; -}; - -// Get the bounds of the range of existing coefficients for a -// given band, in units of coefficient samples. - -template -void get_band_coef_bounds(const coefs &msc, int oct, unsigned int obno, - coef_index_t &ci0_ret, coef_index_t &ci1_ret) -{ - const sliced_coefs &sc = msc.octaves[oct]; - const typename sliced_coefs::slices_t &slices = sc.slices; - if (slices.empty()) { - // Don't try to convert int64t_min/max slices to coef time - ci0_ret = 0; - ci1_ret = 0; - return; - } - // Convert from slices to coefficient samples - ci0_ret = coef_time(*sc.meta, slices.begin_index(), oct, obno); - ci1_ret = coef_time(*sc.meta, slices.end_index(), oct, obno); -} - -template -void get_band_coef_bounds(const coefs &msc, int gbno, - coef_index_t &ci0_ret, coef_index_t &ci1_ret) -{ - int oct; - unsigned int obno; // Band number within octave - bool r = gaborator::bno_split(*msc.meta, gbno, oct, obno, true); - assert(r); - get_band_coef_bounds(msc, oct, obno, ci0_ret, ci1_ret); -} - -// Evaluate the frequency-domain analysis filter kernel of band "bp" -// at frequency "ff" - -template -double eval_kernel(parameters *, band_params *bp, double ff) { - if (bp->dc) { - return gaussian_windowed_lowpass_1(bp->ff, bp->ffsd, ff); - } else { - return norm_gaussian(bp->ffsd, ff - bp->ff); - } -} - -// Evaluate the frequency-domain synthesis filter kernel of band "bp" -// at frequency "ff" - -template -double eval_dual_kernel(parameters *params, band_params *bp, double ff) { - double gain = 1.0; - if (params->lowpass_version == 2) { - if (bp->dc) { - // Adjust the gain of the reconstruction lowpass - // filter to make the overall gain similar to to - // the bandpass region. - double adjusted_overlap = params->sd() / - (log(2) / params->bands_per_octave); - double avg_bandpass_gain = adjusted_overlap * sqrt(M_PI); - gain = avg_bandpass_gain * 0.5; - } - } - return eval_kernel(params, bp, ff) * gain; -} - -template -struct analyzer: public refcounted { - typedef complex C; - struct plan; - - analyzer(const parameters ¶ms_): - params(params_), - max_step_log2(0), - fftsize_max(0), - sftsize_max(0) - { - // Sanity check - assert(params.ff_min < 0.5); - - band_spacing_log2 = params.band_spacing_log2(); - band_spacing = params.band_spacing(); - - // The tuning adjustment, as a log2ff. This is a number between - // 0 and band_spacing_log2, corresponding to a frequency at - // or slightly above the sampling frequency where a band - // center would fall if they actually went that high. - // Tuning is done by increasing the center frequencies of - // all bands by this amount relative to the untuned case - // where one band would fall on fs exactly. - tuning_log2ff = sane_fmod(log2(params.ff_ref), band_spacing_log2); - - // Calculate the total number of bands needed so that - // the lowest band has a frequency <= params.ff_min. - // end_log2ff = the log2ff of the band after the last (just past fs/2); - // the -1 below is the log of the /2 above - double end_log2ff = tuning_log2ff - 1; - n_bandpass_bands_total = - (unsigned int)ceil((end_log2ff - log2(params.ff_min)) / - band_spacing_log2); - n_bands_total = n_bandpass_bands_total + 1; - - top_band_log2ff = end_log2ff - band_spacing_log2; - - ffref_gbno = (int)rint((top_band_log2ff - log2(params.ff_ref)) / - band_spacing_log2); - - // Establish affine transforms for converting between - // log-frequencies (log2(ff)) and bandpass band numbers. - // Derivation: - //ff = exp2(tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2) - //log2(ff) = tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2 - //tuning_log2ff - 1 - (gbno + 1) * band_spacing_log2 = log2(ff) - //-(gbno + 1) * band_spacing_log2 = log2(ff) - tuning_log2ff + 1 - //-(gbno + 1) * band_spacing_log2 = log2(ff) - tuning_log2ff + 1 - //-(gbno + 1) = (log2(ff) - tuning_log2ff + 1) / band_spacing_log2 - //-gbno - 1 = (log2(ff) - tuning_log2ff + 1) / band_spacing_log2 - //-gbno = ((log2(ff) - tuning_log2ff + 1) / band_spacing_log2) + 1 - //gbno = -(((log2(ff) - tuning_log2ff + 1) / band_spacing_log2) + 1) - //gbno = a log2(ff) + b, - // where a = -1 / band_spacing_log2 = -params.bands_per_octave - // and b = -a * tuning_log2ff + a - 1 - // The cast to double is necessary because we can't take the - // negative of an unsigned int. - double a = -(double)params.bands_per_octave; - double b = -a * tuning_log2ff + a - 1; - log2ff_bandpass_band = affine_transform(a, b); - bandpass_band_log2ff = log2ff_bandpass_band.inverse(); - - { - // Precalculate the parameters of the downsampling filter. - // These are the same for all plans, and need to be - // calculated before creating the plans; in particular, we - // need to know the support before we can create the - // plans, because in low-bpo cases, it can determine the - // minimum amount of fat needed. The filter kernel is - // specific to the plan as it depends on the FFT size, - // and will be calculated later. - - // When operating at a high Q, the we will need to use - // large FFTs in any case, and it makes sense to use a - // narrow transition band because we can get that - // essentially for free, and the passband will be - // correspondingly wider, which will allow processing more - // bands at the lower sample rate. Conversely, at low - // Q, we should use a wide transition band so that the - // FFTs can be kept short. - - // The filter is defined in terms of the lower - // (downsampled) sample rate. - - // Make the transition band the same width as the width - // (two-sided support) of a band at ff=0.25, but don't let - // the low edge go below 0.25 to make sure we have a - // reasonable amount of passband left. - double f1 = 0.5; - double f0 = - std::max(f1 - 2 * gaussian_support(ff_sd(0.25), params.max_error), - 0.25); - assert(f0 < f1); - - // The cutoff frequency is in the center of the transition band - double ff = (f0 + f1) * 0.5; - double support = (f1 - f0) * 0.5; - double ff_sd = gaussian_support_inv(support, params.max_error); - - // Calculate and save the time-domain support of the - // downsampling lowpass filter for use in analyze_sliced(). - double time_sd = sd_f2t(ff_sd); - - // Set members - ds_passband = f0; - ds_ff = ff; - ds_ff_sd = ff_sd; - // Since the filter is designed at the lower sample rate, - // ds_time_support is in the unit of lower octave samples - ds_time_support = gaussian_support(time_sd, params.max_error * 0.5); - } - - // Determine the octave structure, packing each band into the - // lowest octave possible. For now, while bpo is restricted - // to integer values, this just means determining how many - // bands need to go in the top octave, and the remaining ones - // will be divided into groups of bpo bands (except possibly - // the last). - int gbno; - for (gbno = bandpass_bands_begin(); gbno < bandpass_bands_end(); gbno++) { - double ff = bandpass_band_ff(gbno); - double ffsd = ff_sd(ff); - double ff_support = gaussian_support(ffsd, params.max_error * 0.5); - // If the bandpass support falls within the downsampling filter - // passband of the next octave, we can switch octaves. - if (params.multirate && ff + ff_support <= ds_passband / 2) - break; - } - n_bands_top_octave = gbno; - - // Figure out the number of octaves, keeping in mind that - // the top octave is of variable size, and DC band is added - // to the bottom octave even if that makes it larger than - // the others. - n_octaves = 1 // The top octave - + (n_bandpass_bands_total - n_bands_top_octave + - (params.bands_per_octave - 1)) / params.bands_per_octave; - - // Calculate the kernel support needed for the lowest-frequency - // bandpass band to use as a basis for an initial estimate of - // the FFT size needed. This duplicates some code at the - // beginning of make_band(). - assert(n_bands_top_octave >= 1); - int low_bp_band = n_bands_top_octave - 1; - double low_bp_band_time_sd = time_sd(band_ff(low_bp_band)); - - double low_bp_band_time_analysis_support = - gaussian_support(low_bp_band_time_sd, params.max_error); - double low_bp_band_time_synthesis_support = - low_bp_band_time_analysis_support * synthesis_support_multiplier(); - - make_zones(); - - // Make analysis plans - // Since ds_time_support is in the unit of lower octave samples, - // we need to multiply it by two to get upper octave samples. - unsigned int max_support = - std::max(ceil(low_bp_band_time_analysis_support), - ds_time_support * 2); - unsigned int size = next_power_of_two(max_support * 2); - ref p; - for (;;) { - p = new plan(this, false, size, max_support); - if (p->ok) - break; - size *= 2; - } - anl_plans.push_back(p); // Smallest possible plan - p = new plan(this, false, size * 2, max_support); - assert(p->ok); - anl_plans.push_back(p); // Next larger plan - - if (params.synthesis) { - // Make synthesis plan (only one for now) - max_support = std::max(ceil(low_bp_band_time_synthesis_support), - ds_time_support * 2); - // Room for at at least the two fats + as much filet - size = next_power_of_two(max_support * 2) * 2; - p = new plan(this, true, size, max_support); - assert(p->ok); - syn_plans.push_back(p); - } - - for (int i = 0; i < (int)anl_plans.size(); i++) - make_band_plans(i, false); - for (int i = 0; i < (int)syn_plans.size(); i++) - make_band_plans(i, true); - - // Find the largest fftsize and sftsize of any plan - for (size_t i = 0; i < anl_plans.size(); i++) { - fftsize_max = std::max(fftsize_max, anl_plans[i]->fftsize); - sftsize_max = std::max(sftsize_max, anl_plans[i]->sftsize_max); - } - for (size_t i = 0; i < syn_plans.size(); i++) { - fftsize_max = std::max(fftsize_max, syn_plans[i]->fftsize); - sftsize_max = std::max(sftsize_max, syn_plans[i]->sftsize_max); - } - - // Lay out the coefficient structures according to the - // synthesis plan if we have one, or the largest analysis - // plan if not. - std::vector> *cmeta_source = - params.synthesis ? &syn_plans : &anl_plans; - ref &largest_plan(((*cmeta_source)[cmeta_source->size() - 1])); - cmeta_any = make_meta(filet_part(largest_plan->fftsize)); - } - - void make_zones() { - // Band number starting at 0 close to fs/2 and increasing - // with decreasing frequency - int tbno = 0; - int oct = 0; - int zno = 0; - // Loop over the octaves, from high to low frequencies, - // creating new zones where needed - for (;;) { - int max_bands_this_octave = (zno == 0) ? - n_bands_top_octave : params.bands_per_octave; - int bands_remaining = n_bandpass_bands_total - tbno; - int bands_this_octave = std::min(max_bands_this_octave, bands_remaining); - int bands_below = bands_remaining - bands_this_octave; - bool dc_zone = (bands_below == 0); - bool dc_adjacent_zone = (bands_below < (int)params.bands_per_octave); - if (zno < 2 || dc_zone || dc_adjacent_zone || - params.bands_per_octave < 6) - { - make_zone(oct, zno, tbno, tbno + bands_this_octave, - dc_zone, bands_below); - zno++; - } - octaves.push_back(octave()); - octaves.back().z = zones[zno - 1].get(); - oct++; - tbno += bands_this_octave; - if (dc_zone) - break; - } - assert(octaves.size() == n_octaves); - } - - // Create a zone consisting of the bandpass bands band0 - // (inclusive) to band1 (exclusive), using the usual gbno - // numbering going from high to low frequencies, and - // possible a lowpass band band1. - - void make_zone(int oct, unsigned int zno, - int band0, int band1, - bool dc_zone, int bands_below) - { - assert(zones.size() == zno); - zone *z = new zone(); - z->zno = zno; - zones.push_back(ref>(z)); - - pod_vector power; - - // Create the real (non-mock) bands, from low to high - // frequency. - if (dc_zone) - // This zone has a lowpass band - push(z->bandparams, make_band(oct, band1, true, false)); - // The actual (non-mock) bandpass bands of this zone - for (int i = band1 - 1; i >= band0; i--) - push(z->bandparams, make_band(oct, i, false, false)); - - if (! dc_zone) { - // There are other zones below this; add mock bands - // to simulate them for purposes of calculating the dual. - - // Identify the lowest frequency of interest in the zone - assert(z->bandparams.size() >= 1); - band_params *low_band = z->bandparams[0].get(); - double zone_bottom_ff = low_band->ff - low_band->ff_support; - - int i = band1; - for (; i < band1 + bands_below; i++) { - band_params *mock_band = make_band(oct, i, false, true); - push(z->mock_bandparams, mock_band); - // There's no point in creating further mock bands - // once they no longer overlap with the current zone. - // The condition used here may cause the creation of - // one more mock band than is actually needed, as it - // is easier to create the band first and check for - // overlap later than the other way round. - if (mock_band->ff + mock_band->ff_support < zone_bottom_ff) { - i++; - break; - } - } - // Create a mock lowpass band. This may correspond to the - // actual lowpass band, or if the loop above exited early, - // it may merely be be a placeholder that serves no real - // purpose other than making the power vector look better. - push(z->mock_bandparams, make_band(oct, i, true, true)); - } - - // If there are other zones above this, add mock bands - // to simulate them for purposes of calculating the dual, - // but only up to the Nyquist frequency of the current - // octave. - int nyquist_band = oct * (int)params.bands_per_octave; - for (int i = band0 - 1; i >= nyquist_band; i--) - push(z->mock_bandparams, make_band(oct, i, false, true)); - - z->n_bands = (unsigned int)z->bandparams.size(); - - // Find the largest coefficient step in the zone, as this will - // determine the necessary alignment of signal slices in time, - // but make it at least two (corresponding max_step_log2 = 1) - // because the downsampling code requires alignement to even - // indices. - unsigned int m = 1; - for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { - m = std::max(m, z->bandparams[obno]->step_log2); - } - z->max_step_log2 = m; - - max_step_log2 = std::max(max_step_log2, m); - } - - // Calculate band parameters for a single band. - // - // If dc is true, this is the DC band, and gbno indicates - // the cutoff frequency; it is one more than the gbno of - // the lowest-frequency bandpass band. - - band_params * - make_band(int oct, double gbno, bool dc, bool mock) { - band_params *bp = new band_params; - if (dc) - // Make the actual DC band cutoff frequency a bit higher, - // by an empirically chosen fraction of a band, to reduce - // power fluctuations. - gbno -= 0.8750526596806952; - - // For bandpass bands, the center frequency, or for the - // lowpass band, the lowpass cutoff frequency, as a - // fractional frequency, in terms of the octave's sample - // rate. - double ff = ldexp(bandpass_band_ff(gbno), oct); - - // Standard deviation of the bandpass Gaussian, as a - // fractional frequency - double ffsd = ff_sd(ff); - double time_sd = sd_f2t(ffsd); - double time_support = - gaussian_support(time_sd, params.max_error); - - // The support of the Gaussian, i.e., the smallest standard - // deviation at which it can be truncated on each side - // without the error exceeding our part of the error budget, - // which is some fraction of params.max_error. Note - // that this is one-sided; the full width of the support - // is 2 * ff_support. - double bp_ff_support = gaussian_support(ffsd, params.max_error * 0.5); - // Additional support for the flat portion of the DC band lowpass - double dc_support = dc ? ff : 0; - // Total frequency-domain support for this band, one-sided - double band_support = bp_ff_support + dc_support; - // Total support needed for this band, two-sided - double band_2support = band_support * 2; - - // Determine the downsampling factor for this band. - int exp = 0; - while (band_2support <= 0.5) { - band_2support *= 2; - exp++; - } - bp->step_log2 = exp; - bp->step = 1U << bp->step_log2; - - bp->dc = dc; - bp->ff = ff; - bp->ff_support = band_support; - bp->time_support = time_support; - bp->ffsd = ffsd; - - return bp; - } - - // Given a fractional frequency, return the standard deviation - // of the frequency-domain window as a fractional frequency - double ff_sd(double ff) const { return params.sd() * ff; } - - // Given a fractional frequency, return the standard deviation - // of the time-domain window in samples. - // - // ff_sd = 1.0 / (tau * t_sd) - // per http://users.ece.gatech.edu/mrichard/ - // Gaussian%20FT%20and%20random%20process.pdf - // and python test program gaussian-overlap.py - // => (tau * t_sd) * ff_sd = 1.0 - // => t_sd = 1.0 / (tau * f_sd) - double time_sd(double ff) const { return 1.0 / (tau * ff_sd(ff)); } - - double q() const { return params.q(); } - - // Find the worst-case time support of the analysis filters, i.e., - // the largest distance in time between a signal sample and a - // coefficient affected by that sample. - double analysis_support() const { - // The lowpass filter is the steepest one - return analysis_support(band_lowpass()); - } - - // Find the time support of the analysis filter for bandpass band - // number gbno - double analysis_support(double gbno) const { - return gaussian_support(time_sd(bandpass_band_ff(gbno)), - params.max_error); - } - - // Ditto for the resynthesis filters. - double synthesis_support() const { - return analysis_support() * synthesis_support_multiplier(); - } - - double synthesis_support(double gbno) const { - return analysis_support(gbno) * synthesis_support_multiplier(); - } - - // The synthesis support multiplier, the factor by which the - // synthesis filters are wider than the analysis filters in - // the time domain. - double synthesis_support_multiplier() const { - if (! params.synthesis) - return 1.0; - // At high ff_min, the top band may be the one with the widest support. - if (params.ff_min > 0.25) - return 4; - if (params.bands_per_octave <= 4) - return 2.5; - return 2.3; - } - - // Get the center frequency of bandpass band "gbno", which - // need not be a valid bandpass band number; out-of-range - // arguments will return extrapolated frequencies based on - // the logarithmic band spacing. - double bandpass_band_ff(double gbno) const { - return exp2(bandpass_band_log2ff(gbno)); - } - - // Get the band number of the bandpass band corresponding - // to the fractional frequency "ff", as a floating point - // number. This is the inverse of bandpass_band_ff(). - double ff_bandpass_band(double ff) const { - return log2ff_bandpass_band(log2(ff)); - } - - int choose_plan(const std::vector> &plans, int64_t size) const - { - unsigned int i = 0; - while (i < plans.size() - 1 && plans[i]->filet_size < size) - i++; - return i; - } - - void - synthesize_one_slice(int oct, int pno, const coefs &msc, - const pod_vector &downsampled, - sample_index_t t0, - T *signal_out, - pod_vector &buf0, // fftsize - pod_vector &buf2, // largest sftsize - pod_vector &buf3 // largest sftsize - ) const - { - const plan &plan(*syn_plans[pno]); - zone &z = *octaves[oct].z; - pod_vector &signal(buf0); - std::fill(signal.begin(), signal.end(), (T)0); - - pod_vector &coefbuf(buf3); - - for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { - band_params *bp = z.bandparams[obno].get(); - band_plan *bpl = &bp->syn_plans[pno]; - - // log2 of the coefficient downsampling factor - int coef_shift = bp->step_log2; - coef_index_t ii = t0 >> coef_shift; - - read(msc, bno_merge(oct, obno), ii, ii + bpl->sftsize, coefbuf.data()); - C *indata = coefbuf.data(); - - pod_vector &sdata(buf2); - - T scale_factor = (T)1 / ((T)params.coef_scale * bpl->sftsize); - - // Apply phase correction, adjust for non-integer center - // frequency, and apply scale factor. Note that phase - // must be calculated in double precision. - - // We can't use bp->ff here because in the case of the - // lowpass band, it's the cutoff rather than the center. - double ff = bpl->center * plan.inv_fftsize_double; - double arg = (params.phase == coef_phase::global) ? - tau * t0 * ff : 0; - C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; - elementwise_product_times_scalar(sdata.data(), indata, - bpl->shift_kernel_conj.data(), - phase_times_scale, bpl->sftsize); - - // Switch to frequency domain - bpl->sft->transform(sdata.data()); - - // Multiply signal spectrum by frequency-domain dual window, - // accumulating result in signal. - - for (unsigned int i = 0; i < bpl->sftsize; i++) { - int iii = (bpl->fq_offset_int + i) & (plan.fftsize - 1); - // Note the ifftshift of the input index, as f=0 - // appears in the middle of the window - C v = sdata[i ^ (bpl->sftsize >> 1)] * bpl->dual_kernel[i]; - // Frequency symmetry - signal[iii] += v; - if (params.lowpass_version == 2 || ! bp->dc) - signal[-iii & (plan.fftsize - 1)] += conj(v); - } - } - - if (oct + 1 < (int) n_octaves) { - // Upsample the downsampled data from the lower octaves - pod_vector &sdata(buf2); - assert(downsampled.size() == plan.dsparams.sftsize); - assert(sdata.size() >= plan.dsparams.sftsize); -#if GABORATOR_USE_REAL_FFT - plan.dsparams.rsft->transform(downsampled.data(), sdata.begin()); -#else - // Real to complex - std::copy(downsampled.begin(), downsampled.end(), sdata.begin()); - plan.dsparams.sft->transform(sdata.data()); -#endif - for (unsigned int i = 0; i < plan.dsparams.sftsize; i++) { - sdata[i] *= plan.dsparams.dual_kernel - [i ^ (plan.dsparams.sftsize >> 1)]; - } - - // This implicitly zero pads the spectrum, by not adding - // anything to the middle part. The splitting of the - // Nyquist band is per http://dsp.stackexchange.com/ - // questions/14919/upsample-data-using-ffts-how-is-this- - // exactly-done but should not really matter because there - // should be no energy there to speak of thanks to the - // windowing above. - - assert(plan.dsparams.sftsize == plan.fftsize / 2); - unsigned int i; - for (i = 0; i < plan.dsparams.sftsize / 2; i++) - signal[i] += sdata[i]; - //C nyquist = sdata[i] * (T)0.5; - C nyquist = sdata[i] * (T)0.5; - signal[i] += nyquist; - signal[i + plan.fftsize / 2] += nyquist; - i++; - for (;i < plan.dsparams.sftsize; i++) - signal[i + plan.fftsize / 2] += sdata[i]; - } - - // Switch to time domain -#if GABORATOR_USE_REAL_FFT - plan.rft->itransform(signal.data(), signal_out); -#else - plan.ft->itransform(signal.data()); - // Copy real part to output - complex2real(signal.begin(), signal.end(), signal_out); -#endif - } - -private: - - // Analyze a signal segment consisting of any number of samples. - // oct is the octave; this is 0 except in recursive calls - // real_signal points to the first sample - // t0 is the sample time of the first sample - // t1 is the sample time of the sample after the last sample - // coefficients are added to msc - - void - analyze_sliced(buffers &buf, int oct, const T *real_signal, - sample_index_t t0, sample_index_t t1, - double included_ds_support, - coefs &msc) const - { - assert(t1 >= t0); - int pno = choose_plan(anl_plans, t1 - t0); - const plan &plan(*anl_plans[pno]); - - // Even though we don't align the FFTs to full filet-size - // slices in this code path, we still need to align them to - // coefficient samples so that we don't have to do expensive - // sub-sample time shifts. Specifically, we need to align - // them to the largest coefficient time step of the octave. - // slice_t0 is the sample time of the first sample in the - // filet (not the FFT as a whole). - zone &z = *octaves[oct].z; - sample_index_t slice_t0 = t0 & ~((1 << z.max_step_log2) - 1); - //printf("slice t0 = %d\n", (int)slice_t0); - - // Find the number of slices we need to divide the signal into. - // We need it ahead of time so that we can size the "downsampled" - // array accordingly. - uint64_t n_slices = ((t1 - slice_t0) + (plan.filet_size - 1)) / plan.filet_size; - - // The "downsampled" array needs to fit one filet per slice + - // the fat on each side, all of half size thanks to downsampling. - // Length of each downsampled slice (including padding) - uint64_t dstotlen = ((n_slices * plan.filet_size) + (2 * plan.fat_size)) >> 1; - - // The range of sample times covered by the "downsampled" array - sample_index_t tmp = slice_t0 - (int)plan.fat_size; - assert((tmp & 1) == 0); - sample_index_t dst0 = tmp >> 1; - sample_index_t dst1 = dst0 + (int64_t)dstotlen; - - // Not all of the "downsampled" array actually contains - // nonzero data. Calculate adjusted bounds to use in the - // recursive analysis so that we don't needlessly analyze - // zeroes. - int ds_support = (int)ceil(ds_time_support - included_ds_support); - sample_index_t dst0a = std::max(dst0, (t0 >> 1) - ds_support); - sample_index_t dst1a = std::min(dst1, (t1 >> 1) + 1 + ds_support); - - // Buffer for the downsampled signal. Since the size depends - // on the total amount of signal analyzed in this call (being - // about half of it), it can't be preallocated, but has to be - // dynamically allocated in each call. - pod_vector downsampled(dstotlen); - // "downsampled" will be added to, not assigned to, so we need - // to set it to zero initially. - std::fill(downsampled.begin(), downsampled.end(), 0); - - auto slice(buf.template get(5, plan.fftsize)); - // Clear the fat on both ends (once) - std::fill(slice.data(), slice.data() + plan.fat_size, 0); - std::fill(slice.data() + slice.size() - plan.fat_size, - slice.data() + slice.size(), 0); - - // For each slice. Note that slice_i counts from 0, not from - // the slice index of the first slice. - for (uint64_t slice_i = 0; slice_i < n_slices; slice_i++) { - if (slice_t0 >= t1) - break; - sample_index_t slice_t1 = std::min(slice_t0 + plan.filet_size, t1); - // Copy into filet part of aligned buffer, possibly zero padding - // if the remaining signal is shorter than a full slice. - copy_overlapping_zerofill(slice.data() + (int)plan.fat_size, - plan.filet_size, - real_signal, - t0 - slice_t0, - t1 - slice_t0); - // Analyze the slice - auto spectrum(buf.template get(1, plan.fftsize)); -#if GABORATOR_USE_REAL_FFT - plan.rft->transform(slice.data(), spectrum.data()); -#else - // Real to complex - auto signal(buf.template get(0, plan.fftsize)); - std::copy(slice.data(), slice.data() + plan.fftsize, signal.begin()); - plan.ft->transform(signal.data(), spectrum.data()); -#endif - auto tmp(buf.template get(2, plan.sftsize_max)); - auto coefbuf(buf.template get(4, plan.sftsize_max)); - - T scale_factor = (T)params.coef_scale * plan.inv_fftsize_t; - - for (unsigned int obno = 0; obno < z.bandparams.size(); obno++) { - band_params *bp = z.bandparams[obno].get(); - band_plan *bpl = &bp->anl_plans[pno]; - C *sdata = tmp.data(); - - // Multiply a slice of the spectrum by the frequency- - // domain window and store in sdata. - // - // We need to take care not to overrun the beginning or - // end of the spectrum - for the dc band, we always - // need to wrap around to negative frequencies, and - // potentially it could happen with other bands, too, - // if they are really wide. To avoid the overhead of - // checking in the inner loop, use a separate slow path - // for the rare cases where wrapping happens. - - int start_index = bpl->fq_offset_int; - int end_index = bpl->fq_offset_int + bpl->sftsize; - if (start_index >= 0 && end_index < (int)((plan.fftsize >> 1) + 1)) { - // Fast path: the slice lies entirely within the - // positive-frequency half of the spectrum (including - // DC and Nyquist). - elementwise_product(sdata, - spectrum.data() + start_index, - bpl->kernel.data(), - bpl->sftsize); - } else { - // Slow path - for (size_t i = 0; i < bpl->sftsize; i++) - sdata[i] = get_real_spectrum_coef(spectrum.data(), - (int)(start_index + i), plan.fftsize) * bpl->kernel[i]; - } - // The band center frequency is at the center of the - // spectrum slice and at the center of the window, so - // it also ends up at the center of sdata. The center - // frequency of the band is considered f=0, so for the - // ifft, it should be at index 0, not the center. - // Therefore, in principle we should perform an - // ifftshift of sdata here before the ifft, but since - // the time-domain data are going to be multiplied by - // the shift kernel anyway, the ifftshift is baked - // into the shift kernel by flipping the sign of every - // other element so that it is effectively free. - - // Switch to time domain - auto band(buf.template get(3, plan.sftsize_max)); - bpl->sft->itransform(sdata, band.data()); - - // Apply ifftshift, adjust for non-integer center - // frequency, correct phase, scale amplitude, and add - // to the output coefficients. - double ff = bpl->center * plan.inv_fftsize_double; - double arg; - if (params.phase == coef_phase::global) - arg = -tau * (slice_t0 - plan.fat_size) * ff; - else - arg = 0; - C phase_times_scale = C(cos(arg), sin(arg)) * scale_factor; - elementwise_product_times_scalar(coefbuf.data(), band.data(), - bpl->shift_kernel.data(), - phase_times_scale, - bpl->sftsize); - // log2 of the coefficient downsampling factor - int coef_shift = bp->step_log2; - assert(((slice_t0 - (int)plan.fat_size) & ((1 << coef_shift) - 1)) == 0); - coef_index_t ii = (slice_t0 - (int)plan.fat_size) >> coef_shift; - - // Only part of coefbuf contains substantially nonzero - // data: that corresponding to the signal interval - // t0..t1 + the actual support of the filter for this band. - // There's no point adding the zeros to the coefficients, - // so trim. - int support = (int)ceil(bp->time_support); - coef_index_t ii0 = std::max(ii, (t0 - support) >> coef_shift); - coef_index_t ii1 = std::min(ii + bpl->sftsize, - ((t1 + support) >> coef_shift) + 1); - add(msc, bno_merge(oct, obno), ii0, ii1, coefbuf.data() + (ii0 - ii)); - } - - // Downsample - if (oct + 1 < (int) n_octaves) { - T *downsampled_dst = downsampled.data() + - slice_i * (plan.filet_size >> 1); - - auto sdata(buf.template get(2, plan.sftsize_max)); - // This is using a larger buffer than we actually need - auto ddata(buf.template get(0, plan.sftsize_max)); - assert(ddata.size() >= plan.dsparams.sftsize); - // Extract the low-frequency part of "spectrum" into "sdata" - // and multiply it by the lowpass filter frequency response. - // This means both positive and negative low frequencies. - size_t half_size = plan.dsparams.sftsize >> 1; - assert(plan.fftsize - half_size == 3 * half_size); -#if GABORATOR_USE_REAL_FFT - // Positive frequencies - elementwise_product(sdata.data(), spectrum.data(), - plan.dsparams.kernel.data() + half_size, - half_size); - // Nyquist - sdata[half_size] = 0; - // Use the same buffer as the complex FFT, but as floats - T *real_ddata = reinterpret_cast(ddata.data()); - plan.dsparams.rsft->itransform(sdata.data(), real_ddata); - // Only accumulate nonzero part - size_t n = ((slice_t1 - slice_t0) + 2 * (int)plan.fat_size + 1) >> 1; - for (size_t i = 0; i < n; i++) - downsampled_dst[i] += real_ddata[i]; -#else - // Positive frequencies - elementwise_product(sdata.data(), spectrum.data(), - plan.dsparams.kernel.data() + half_size, - half_size); - // Negative requencies - elementwise_product(sdata.data() + half_size, - spectrum.data() + plan.fftsize - half_size, - plan.dsparams.kernel.data(), half_size); - // Convert to time domain - plan.dsparams.sft->itransform(sdata.data(), ddata.data()); - for (unsigned int i = 0; i < plan.dsparams.sftsize; i++) - downsampled_dst[i] += ddata[i].real(); -#endif - } - - // Next slice - slice_t0 = slice_t1; - } - - // Recurse - if (oct + 1 < (int)n_octaves) - analyze_sliced(buf, oct + 1, downsampled.data() + (dst0a - dst0), - dst0a, dst1a, ds_time_support / 2, msc); - } - - // Resynthesize audio from the coefficients in "msc". The audio will - // cover samples from t0 (inclusive) to t1 (exclusive), and is stored - // starting at *real_signal, which must have room for (t1 - t0) - // samples. The octave "oct" is 0 except in recursive calls. - - void - synthesize_sliced(int oct, const coefs &msc, - sample_index_t t0, sample_index_t t1, - T *real_signal) const - { - int pno = choose_plan(syn_plans, t1 - t0); - const plan &plan(*syn_plans[pno]); - - // XXX clean up - no need to pass support arg - slice_index_t si0 = plan.affected_slice_b(t0, plan.oct_support); - slice_index_t si1 = plan.affected_slice_e(t1, plan.oct_support); - - // sub_signal holds the reconstructed subsampled signal from - // the lower octaves, for the entire time interval covered by - // the slices - int sub_signal_len = ((si1 - si0) * plan.filet_size + 2 * plan.fat_size) / 2; - pod_vector sub_signal(sub_signal_len); - std::fill(sub_signal.begin(), sub_signal.end(), 0); - if (oct + 1 < (int)n_octaves) { - int64_t sub_t0 = si0 * (plan.filet_size / 2); - int64_t sub_t1 = sub_t0 + sub_signal_len; - // Recurse - assert(sub_t1 - sub_t0 == (int64_t)sub_signal.size()); - synthesize_sliced(oct + 1, msc, sub_t0, sub_t1, sub_signal.data()); - } - - // Allocate buffers for synthesize_one_slice(), to be shared - // between successive calls to avoid repeated allocation - pod_vector buf0(plan.fftsize); - //pod_vector buf1(fftsize); - pod_vector buf2(plan.sftsize_max); - pod_vector buf3(plan.sftsize_max); - - pod_vector downsampled(plan.dsparams.sftsize); - pod_vector signal_slice(plan.fftsize); - - // For each slice - for (slice_index_t si = si0; si < si1; si++) { - sample_index_t slice_t0 = si * plan.filet_size; - - // Copy downsampled signal to "downsampled" for upsampling - if (oct + 1 < (int) n_octaves) { - int bi = (si - si0) * filet_part(plan.dsparams.sftsize); - int ei = bi + plan.dsparams.sftsize; - assert(bi >= 0); - assert(ei <= (int)sub_signal.size()); - std::copy(sub_signal.begin() + bi, - sub_signal.begin() + ei, - downsampled.begin()); - } - - synthesize_one_slice(oct, pno, msc, downsampled, slice_t0, - signal_slice.data(), buf0, buf2, buf3); - - // Copy overlapping part - sample_index_t b = std::max(slice_t0 + plan.fat_size, t0); - sample_index_t e = std::min(slice_t0 + plan.fftsize - plan.fat_size, t1); - for (sample_index_t i = b; i < e; i++) - real_signal[i - t0] = signal_slice[i - slice_t0]; - } - } - -public: - // The main analysis entry point. - // The resulting coefficients are added to any existing - // coefficients in "msc". - - void analyze(const T *real_signal, sample_index_t t0, sample_index_t t1, - coefs &msc, int n_threads = 1) const - { - analyze1(real_signal, t0, t1, msc, n_threads, 1); - } - - void analyze1(const T *real_signal, sample_index_t t0, sample_index_t t1, - coefs &msc, int n_threads, int level) const - { - assert(msc.octaves.size() == n_octaves); - (void)n_threads; - buffers buf(fftsize_max, sftsize_max); - analyze_sliced(buf, 0, real_signal, t0, t1, 0, msc); - } - - // The main synthesis entry point - - void - synthesize(const coefs &msc, sample_index_t t0, sample_index_t t1, - T *real_signal, int n_threads = 1) const - { - assert(params.synthesis); - (void)n_threads; - synthesize_sliced(0, msc, t0, t1, real_signal); - } - - bool bno_split(int gbno, int &oct, unsigned int &obno, bool dc) const { - return gaborator::bno_split(*cmeta_any, gbno, oct, obno, dc); - } - - int bno_merge(int oct, unsigned int obno) const { - return gaborator::bno_merge(*cmeta_any, oct, obno); - } - - // Get the bounds of the range of existing coefficients for all bands, - // in units of signal samples. - void get_coef_bounds(const coefs &msc, - sample_index_t &si0_ret, sample_index_t &si1_ret) - const - { - // The greatest coefficient range typically occurs in the - // lowest bandpass band, but this is not always the case, - // so to be certain, check them all. - sample_index_t min_si0 = INT64_MAX; - sample_index_t max_si1 = INT64_MIN; - for (int band = bands_begin(); band != bands_end(); band++) { - coef_index_t ci0, ci1; - get_band_coef_bounds(msc, band, ci0, ci1); - // Convert from coefficient samples to signal samples - int exp = band_scale_exp(band); - sample_index_t si0 = shift_left(ci0, exp); - sample_index_t si1 = shift_left(ci1 - 1, exp) + 1; - min_si0 = std::min(min_si0, si0); - max_si1 = std::max(max_si1, si1); - } - si0_ret = min_si0; - si1_ret = max_si1; - } - - unsigned int band_step_log2(int gbno) const { - return gaborator::band_step_log2(*cmeta_any, gbno); - } - - int bandpass_bands_begin() const { return 0; } - int bandpass_bands_end() const { return n_bands_total - 1; } - - int bands_begin() const { return 0; } - int bands_end() const { return n_bands_total; } - - int band_lowpass() const { return n_bands_total - 1; } - int band_ref() const { return ffref_gbno; } - - // Get the center frequency of band number gbno as a fractional - // frequency. gbno must be a valid band number. For the lowpass - // band, this returns zero. - double band_ff(int gbno) const { - if (gbno == band_lowpass()) - return 0; - return bandpass_band_ff(gbno); - } - - ~analyzer() { - } - - // Get the base 2 logarithm of the downsampling factor of - // band "obno" in octave "oct" - int band_scale_exp(int oct, unsigned int obno) const { - return gaborator::band_scale_exp(*cmeta_any->octaves[oct].z, oct, obno); - } - - // Get the base 2 logarithm of the downsampling factor of - // band "gbno" - int band_scale_exp(int gbno) const { - int oct; - unsigned int obno; // Band number within octave - bool r = bno_split(gbno, oct, obno, true); - assert(r); - return band_scale_exp(oct, obno); - } - - // Get the base 2 logarithm of the highest downsampling factor of - // any band - int band_scale_exp_max() const { - return band_scale_exp(bandpass_bands_end() - 1); - } - - - // Find the sample time of the band "gbno" coefficient closest to - // time "t". "gbno" must be a valid band number. - sample_index_t nearest_coef_sample(int gbno, double t) const { - int shift = band_step_log2(gbno); - return shift_left((sample_index_t) round(ldexp(t, -shift)), shift); - } - // Find the highest coefficient sample time less than or equal to - // "t" for band "gbno". "gbno" must be a valid band number. - sample_index_t floor_coef_sample(int gbno, double t) const { - int shift = band_step_log2(gbno); - return shift_left((sample_index_t) floor(ldexp(t, -shift)), shift); - } - // Find the lowestt coefficient sample time greater than or equal - // to "t" for band "gbno". "gbno" must be a valid band number. - sample_index_t ceil_coef_sample(int gbno, double t) const { - int shift = band_step_log2(gbno); - return shift_left((sample_index_t) ceil(ldexp(t, -shift)), shift); - } - - // Members initialized in the constructor, and listed in - // order of initialization - parameters params; - double band_spacing_log2; - double band_spacing; - double tuning_log2ff; - affine_transform log2ff_bandpass_band; - affine_transform bandpass_band_log2ff; - unsigned int n_bandpass_bands_total; - unsigned int n_bands_top_octave; - - struct plan: public refcounted { - plan(const plan &) = delete; - plan(analyzer *anl, bool synthesis_, unsigned int fftsize_, double support_): - ok(false), - synthesis(synthesis_), - fftsize(fftsize_), - oct_support(support_), - sftsize_max(0) - { - fftsize_log2 = whichp2(fftsize); - - inv_fftsize_double = 1.0 / fftsize; - inv_fftsize_t = (T) inv_fftsize_double; - -#if GABORATOR_USE_REAL_FFT - rft = pool, int>::shared.get(fftsize); -#else - ft = pool, int>::shared.get(fftsize); -#endif - // Set up the downsampling parameters in dsparams. - - // Downsampling is always by a factor of two. - // dsparams.sftsize is the size of the FFT used to go back to - // the time domain after discarding the top half of the - // spectrum. - dsparams.sftsize = fftsize >> 1; - dsparams.kernel.resize(dsparams.sftsize); - if (synthesis) - dsparams.dual_kernel.resize(dsparams.sftsize); - - // Use the convolution of a rectangle and a Gaussian. - // A piecewise function composed from two half-gaussians - // joined by a horizontal y=1 segment is not quite smooth - // enough. Put the passband in the middle. - for (int i = 0; i < (int)dsparams.sftsize; i++) - dsparams.kernel[i] = - gaussian_windowed_lowpass_1(anl->ds_ff, anl->ds_ff_sd, - ((double)i / dsparams.sftsize) - 0.5); - - if (synthesis) { - // The dual_kernel field of the downsampling pseudo-band holds - // the upsampling filter, identical to the downsampling filter - // except for amplitude scaling. - std::copy(dsparams.kernel.begin(), dsparams.kernel.end(), - dsparams.dual_kernel.begin()); - } - // Prescale the downsampling filter - scale_vector(dsparams.kernel, inv_fftsize_double); - if (synthesis) { - // Prescale the upsampling filter - scale_vector(dsparams.dual_kernel, 1.0 / dsparams.sftsize); - } -#if GABORATOR_USE_REAL_FFT - dsparams.rsft = pool, int>::shared.get(dsparams.sftsize); -#else - dsparams.sft = pool, int>::shared.get(dsparams.sftsize); -#endif - // It may be possible to reduce the size of the fat from 1/4 - // of the fftsize, but we need to keep things aligned with the - // coefficients, and it needs to be even for downsampling. - if (! synthesis) { - unsigned int align = 1 << std::max(anl->max_step_log2, 2U); - fat_size = (oct_support + (align - 1)) & ~(align - 1); - // There must be room for at least one signal sample in each - // half of the FFT; it can't be all fat - if (!(fat_size < (fftsize >> 1))) - return; // fail - } else { - fat_size = fftsize >> 2; - } - filet_size = fftsize - 2 * fat_size; - - // Constructor was successful - ok = true; - } - - // Index of first slice affected by sample at t0 - - // fft number i covers the sample range - // t = (i * filetsize .. i * filetsize + (fftsize - 1)) - // t >= i * filetsize and t < i * filetsize + fftsize - // A sample at t affects ffts i where - // i <= t / filetsize and - // i > (t - fftsize) / filetsize - // the filet of fft number i covers the sample range - // (fat + (i * filetsize) .. fat + (i * filetsize) + (filetsize - 1)) - // - // However, due to the FFT size being rounded up to a power of two, - // the outermost parts have near-zero weights and can be ignored; - // this is done by adjusting the time values by the width of that - // outermost part, which is (fatsize - support) - - slice_index_t affected_slice_b(sample_index_t t0, unsigned int support) const { - return floor_div(t0 - fftsize + (fat_part(fftsize) - support), filet_part(fftsize)) + 1; - } - - // Index of first slice not affected by sample at t1 - slice_index_t affected_slice_e(sample_index_t t1, unsigned int support) const { - return floor_div(t1 - 1 - (fat_part(fftsize) - support), filet_part(fftsize)) + 1; - } - - bool ok; - bool synthesis; - - unsigned int fftsize_log2; // log2(fftsize) - unsigned int fftsize; // The size of the main FFT, a power of two. - unsigned int fat_size; - unsigned int filet_size; - - // The width of the widest filter in the time domain, in - // octave subsamples - unsigned int oct_support; - - double inv_fftsize_double; // 1.0 / fftsize - T inv_fftsize_t; // 1.0f / fftsize (if using floats) - - unsigned int sftsize_max; // The size of the largest band FFT, a power of two - downsampling_params dsparams; - - // Fourier transform object for transforming a full slice -#if GABORATOR_USE_REAL_FFT - rfft *rft; -#else - fft *ft; -#endif - }; - - // Calculate per-plan, per-band coefficients for plan "pno", - // a synthesis plan if "syn" is true, otherwise an analysis plan. - - void make_band_plans(int pno, bool syn) { - std::vector::plan>> &plans - (syn ? syn_plans : anl_plans); - plan &plan(*plans[pno].get()); - - for (int zno = 0; zno < (int)zones.size(); zno++) { - zone *z = zones[zno].get(); - - make_band_plans_2(z->bandparams, pno, syn, false); - make_band_plans_2(z->mock_bandparams, pno, syn, true); - - if (plan.synthesis) { - // Accumulate window power for calculating dual - std::vector power(plan.fftsize); - // Real bands - for (unsigned int i = 0; i < z->bandparams.size(); i++) { - band_params *bp = z->bandparams[i].get(); - band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; - accumulate_power(plan, bp, bpl, power.data()); - } - // Mock bands - for (unsigned int i = 0; i < z->mock_bandparams.size(); i++) { - band_params *bp = z->mock_bandparams[i].get(); - band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; - accumulate_power(plan, bp, bpl, power.data()); - } - - // Calculate duals - for (unsigned int obno = 0; obno < z->bandparams.size(); obno++) { - band_params *bp = z->bandparams[obno].get(); - band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; - for (unsigned int i = 0; i < bpl->sftsize; i++) { - // ii = large-FFT bin number - int ii = i + bpl->fq_offset_int; - bpl->dual_kernel[i] /= power[ii & (plan.fftsize - 1)]; - } - // The analysis kernels are no longer needed - bpl->kernel = std::vector(); - bpl->shift_kernel = pod_vector(); - } - z->mock_bandparams.clear(); - } - } - } - - void make_band_plans_2(std::vector>> &bv, int pno, - bool syn, bool mock) - { - std::vector::plan>> &plans - (syn ? syn_plans : anl_plans); - plan &plan(*plans[pno].get()); - - for (unsigned int obno = 0; obno < bv.size(); obno++) { - band_params *bp = bv[obno].get(); - std::vector> *bplv = syn ? &bp->syn_plans : &bp->anl_plans; - // XXX redundant resizes - bplv->resize(plans.size()); - band_plan *bpl = &(*bplv)[pno]; - - // Note that bp->step_log2 cannot be negative, meaning - // that the bands can only be subsampled, not oversampled. - unsigned int sftsize = plan.fftsize >> bp->step_log2; - - // PFFFT has a minimum size - sftsize = std::max(sftsize, (unsigned int)GABORATOR_MIN_FFT_SIZE); - - bpl->sftsize = sftsize; - bpl->sftsize_log2 = whichp2(bpl->sftsize); - - if (! mock) { - plan.sftsize_max = std::max(plan.sftsize_max, bpl->sftsize); - bpl->sft = pool, int>::shared.get(bpl->sftsize); - } - - bpl->kernel.resize(bpl->sftsize); - bpl->shift_kernel.resize(bpl->sftsize); - if (plan.synthesis) { - bpl->dual_kernel.resize(bpl->sftsize); - bpl->shift_kernel_conj.resize(bpl->sftsize); - } - - if (bp->dc) - bpl->center = 0; - else - bpl->center = bp->ff * plan.fftsize; - bpl->icenter = (int)rint(bpl->center); - bpl->fq_offset_int = bpl->icenter - (bpl->sftsize >> 1); - - // Calculate frequency-domain window kernel, possibly with - // wrap-around - for (unsigned int i = 0; i < bpl->sftsize; i++) - bpl->kernel[i] = 0; - // i loops over the kernel, with i=0 at the center. - // The range is twice the support on each side so that - // any excess space in the kernel due to rounding up - // the size to a power of two is filled in with actual - // Gaussian values rather than zeros. - int fq_support = (int)ceil(bp->ff_support * plan.fftsize); - for (int i = - 2 * fq_support; i < 2 * fq_support; i++) { - // ii = large-FFT band number of this kernel sample - int ii = i + bpl->fq_offset_int + (int)bpl->sftsize / 2; - // this_ff = fractional frequency of this kernel sample - double this_ff = ii * plan.inv_fftsize_double; - // ki = kernel index - int ki = ii - bpl->fq_offset_int; - // When sftsize == fftsize, the support of the kernel can - // exceed sftsize, and in this case, it should be allowed - // to wrap so that it remains smooth. When sftsize < fftsize, - // sftsize is large enough for the support and no wrapping - // is needed or wanted. - if (bpl->kernel.size() == plan.fftsize && !mock) { - bpl->kernel[ki & (plan.fftsize - 1)] += - eval_kernel(¶ms, bp, this_ff); - if (plan.synthesis) - bpl->dual_kernel[ki & (plan.fftsize - 1)] += - eval_dual_kernel(¶ms, bp, this_ff); - } else { - if (ki >= 0 && ki < (int)bpl->kernel.size()) { - bpl->kernel[ki] += eval_kernel(¶ms, bp, this_ff); - if (plan.synthesis) - bpl->dual_kernel[ki] = eval_dual_kernel(¶ms, bp, this_ff); - } - } - } - } - - // Calculate complex exponentials for non-integer center - // frequency adjustment and phase convention adjustment - for (unsigned int obno = 0; obno < bv.size(); obno++) { - band_params *bp = bv[obno].get(); - band_plan *bpl = syn ? &bp->syn_plans[pno] : &bp->anl_plans[pno]; - for (unsigned int i = 0; i < bpl->sftsize; i++) { - double center = - (params.phase == coef_phase::global) ? bpl->center : 0; - double arg = tau * ((double)i / bpl->sftsize) * -(center - bpl->icenter); - C t(cos(arg), sin(arg)); - // Apply ifftshift of spectrum in time domain - bpl->shift_kernel[i] = (i & 1) ? -t : t; - if (plan.synthesis) - // Conjugate kernel does not have ifftshift - bpl->shift_kernel_conj[i] = conj(t); - } - } - } - - // Add the power of the kernel in "*bp" to "power" - void - accumulate_power(plan &plan, band_params *bp, band_plan *bpl, T *power) { - for (unsigned int i = 0; i < bpl->sftsize; i++) { - // ii = large-FFT bin number - unsigned int ii = i + bpl->fq_offset_int; - ii &= plan.fftsize - 1; - assert(ii >= 0 && ii < plan.fftsize); - T p = bpl->kernel[i] * bpl->dual_kernel[i]; - power[ii] += p; - if (params.lowpass_version == 2 || ! bp->dc) { - unsigned int ni = -ii; - ni &= plan.fftsize - 1; - power[ni] += p; - } - } - } - - // Create coefficient metadata based on a slice length - coefs_meta *make_meta(int slice_len) const { - coefs_meta *cmeta = new coefs_meta; - cmeta->n_octaves = n_octaves; - cmeta->n_bands_total = n_bands_total; - cmeta->bands_per_octave = params.bands_per_octave; - cmeta->slice_len = slice_len; - cmeta->zones.resize(zones.size()); - for (unsigned int zi = 0; zi < zones.size(); zi++) { - zone *z = zones[zi].get(); - typename zone_coefs_meta::band_vector bv(z->bandparams.size()); - for (unsigned int i = 0; i < z->bandparams.size(); i++) { - unsigned int step_log2 = z->bandparams[i]->step_log2; - bv[i].step_log2 = step_log2; - bv[i].slice_len_log2 = whichp2(slice_len) - step_log2; - bv[i].slice_len = 1 << bv[i].slice_len_log2; - } - cmeta->zones[zi].init(bv); - } - cmeta->octaves.resize(octaves.size()); - int tbno = 0; - for (unsigned int i = 0; i < n_octaves; i++) { - cmeta->octaves[i].z = &cmeta->zones[this->octaves[i].z->zno]; - cmeta->octaves[i].n_bands_above = tbno; - tbno += cmeta->octaves[i].z->bands.size(); - } - return cmeta; - } - - std::vector> octaves; // Per-octave parameters - std::vector>> zones; - unsigned int max_step_log2; - - std::vector> anl_plans; - std::vector> syn_plans; - - unsigned int n_octaves; - unsigned int n_bands_total; // Total number of frequency bands, including DC - - double top_band_log2ff; // log2 of fractional frequency of the highest-frequency band - int ffref_gbno; // Band number of the reference frequency - - // Width of the downsampling filter passband in terms of the - // downsampled sample rate (between 0.25 and 0.5) - double ds_passband; - double ds_ff; // Downsampling filter -6 dB transition frequency - double ds_ff_sd; // Downsampling filter standard deviation - double ds_time_support; // Downsampling filter time-domain kernel support, each side - - unsigned int fftsize_max; // Largest FFT size of any plan - unsigned int sftsize_max; // Largest SFT size of any plan - - ref cmeta_any; -}; - - -// Iterate over the slices of a row (band) having slice length -// 2^sh that contain coefficients with indices ranging from i0 -// (inclusive) to i1 (exclusive), and call the function f for -// each such slice (full or partial), with the arguments -// -// sli - slice index -// bvi - index of first coefficient to process within the slice -// len - number of coefficients to process within the slice - -template -void foreach_slice(unsigned int sh, coef_index_t i0, coef_index_t i1, F f) { - // Note that this can be called with i0 > i1 and needs to handle - // that case gracefully. - // Band size (power of two) - int bsize = 1 << sh; - // Adjust for t=0 being outside the filet - int fatsize = bsize >> 1; - i0 -= fatsize; - i1 -= fatsize; - coef_index_t i = i0; - while (i < i1) { - // Slice index - slice_index_t sli = i >> sh; - // Band vector index - unsigned int bvi = i & (bsize - 1); - unsigned int len = bsize - bvi; - unsigned int remain = (unsigned int)(i1 - i); - if (remain < len) - len = remain; - f(sli, bvi, len); - i += len; - } -} - -// As foreach_slice, but call the "process_existing_slice" method of -// the given "dest" object for each full or partial slice of -// coefficients, and/or the "process_missing_slice" method for each -// nonexistent slice. -// -// Template parameters: -// T is the spectrogram value type -// D is the dest object type -// C is the coefficient type - -template > -struct row_foreach_slice { - typedef C value_type; - row_foreach_slice(const coefs &msc, - int oct_, unsigned int obno_): - oct(oct_), obno(obno_), sc(msc.octaves[oct]), - sh(sc.meta->bands[obno].slice_len_log2) - { - assert(oct < (int)msc.octaves.size()); - } -public: - void operator()(coef_index_t i0, coef_index_t i1, D &dest) const { - foreach_slice(sh, i0, i1, - [this, &dest](slice_index_t sli, unsigned int bvi, unsigned int len) { - oct_coefs *c = get_existing_coefs(sc, sli); - if (c) { - dest.process_existing_slice(c->bands[obno] + bvi, len); - } else { - dest.process_missing_slice(len); - } - }); - } - int oct; - unsigned int obno; - const sliced_coefs ≻ - unsigned int sh; -}; - -// Helper class for row_source - -template -struct writer_dest { - writer_dest(OI output_): output(output_) { } - void process_existing_slice(C *bv, size_t len) { - // Can't use std::copy here because it takes the output - // iterator by value, and using the return value does not - // work, either. - for (size_t i = 0; i < len; i++) - *output++ = bv[i]; - } - void process_missing_slice(size_t len) { - for (size_t i = 0; i < len; i++) - *output++ = C(); - } - OI output; -}; - -// Retrieve a sequence of coefficients from a row (band) in the -// spectrogram, with indices ranging from i0 to i1. The indices can -// be negative, and can extend outside the available data, in which -// case zero is returned. The coefficients are written through the -// output iterator "output". -// Template arguments: -// T is the spectrogram value type -// OI is the output iterator type -// C is the coefficient value type -// Note defaults for template arguments defined in forward declaration. - -template -struct row_source { - row_source(const coefs &msc_, - int oct_, unsigned int obno_): - slicer(msc_, oct_, obno_) - { } - OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { - writer_dest dest(output); - slicer(i0, i1, dest); - return dest.output; - } - row_foreach_slice, C> slicer; -}; - -// The opposite of row_source: store a sequence of coefficients into -// a row (band) in the spectrogram. This duplicates quite a lot of -// the row_source code above (without comments); the main part that's -// different is marked by the comments "Begin payload" and "End -// payload". Other differences: iterator is called II rather than OI, -// and the coefs are not const. - -template -struct row_dest { - typedef C value_type; - row_dest(coefs &msc, - int oct_, unsigned int obno_): - oct(oct_), obno(obno_), sc(msc.octaves[oct]), - sh(sc.meta->bands[obno].slice_len_log2) - { - assert(oct < (int)msc.octaves.size()); - } -public: - II operator()(coef_index_t i0, coef_index_t i1, II input) const { - assert(i0 <= i1); - int bsize = 1 << sh; - int fatsize = bsize >> 1; - i0 -= fatsize; - i1 -= fatsize; - coef_index_t i = i0; - while (i < i1) { - slice_index_t sli = i >> sh; - unsigned int bvi = i & (bsize - 1); - unsigned int len = bsize - bvi; - unsigned int remain = (unsigned int)(i1 - i); - if (remain < len) - len = remain; - int bvie = bvi + len; - // Begin payload - oct_coefs *c = &get_or_create_coefs(sc, sli); - C *bv = c->bands[obno]; - for (int j = bvi; j < bvie; j++) - bv[j] = *input++; - i += len; - // End payload - } - return input; - } - int oct; - unsigned int obno; - sliced_coefs ≻ - unsigned int sh; -}; - -// One more set of duplicated code, now for adding to coefficients - -template -struct row_add_dest { - typedef C value_type; - row_add_dest(coefs &msc, - int oct_, unsigned int obno_): - oct(oct_), obno(obno_), sc(msc.octaves[oct]), - sh(sc.meta->bands[obno].slice_len_log2) - { - assert(oct < (int)msc.octaves.size()); - } -public: - II operator()(coef_index_t i0, coef_index_t i1, II input) const { - assert(i0 <= i1); - int bsize = 1 << sh; - int fatsize = bsize >> 1; - i0 -= fatsize; - i1 -= fatsize; - coef_index_t i = i0; - while (i < i1) { - slice_index_t sli = i >> sh; - unsigned int bvi = i & (bsize - 1); - unsigned int len = bsize - bvi; - unsigned int remain = (unsigned int)(i1 - i); - if (remain < len) - len = remain; - int bvie = bvi + len; - // Begin payload - oct_coefs *c = &get_or_create_coefs(sc, sli); - C *bv = c->bands[obno]; - for (int j = bvi; j < bvie; j++) - bv[j] += *input++; - i += len; - // End payload - } - return input; - } - int oct; - unsigned int obno; - sliced_coefs ≻ - unsigned int sh; -}; - -// Helper for process() below. Here, the function f() operates on an -// array of consecutive coefficient samples rather than a single -// sample. - -template -void apply_to_slice(bool create, - F f, - int b0, // = INT_MIN - int b1, // = INT_MAX - sample_index_t st0, // = INT64_MIN - sample_index_t st1, // = INT64_MAX - coefs& coefs0, - coefs&... coefsi) -{ - b0 = std::max(b0, 0); - b1 = std::min(b1, (int)coefs0.meta->n_bands_total); - for (int band = b0; band < b1; band++) { - int oct; - unsigned int obno; - bool valid = gaborator::bno_split(*coefs0.meta, band, oct, obno, true); - assert(valid); - - int exp = coefs0.meta->octaves[oct].z->bands[obno].step_log2 + oct; - int time_step = 1 << exp; - - coef_index_t ci0 = (st0 + time_step - 1) >> exp; - coef_index_t ci1 = ((st1 - 1) >> exp) + 1; - if (! create) { - // Restrict to existing coefficient index range - coef_index_t cib0, cib1; - get_band_coef_bounds(coefs0, oct, obno, cib0, cib1); - ci0 = std::max(ci0, cib0); - ci1 = std::min(ci1, cib1); - } - unsigned int sh = coefs0.meta->octaves[oct].z->bands[obno].slice_len_log2; - sample_index_t st = shift_left(ci0, exp); - foreach_slice(sh, ci0, ci1, - [&](slice_index_t sli, unsigned int bvi, - unsigned int len) - { - oct_coefs *c = create ? - &get_or_create_coefs(coefs0.octaves[oct], sli) : - get_existing_coefs(coefs0.octaves[oct], sli); - if (c) { - // p0 points to coefficient from the first set - C0 *p0 = c->bands[obno] + bvi; - f(band, st, time_step, len, p0, - get_or_create_coefs(coefsi.octaves[oct], sli).bands[obno] + bvi...); - } - st += len * time_step; - }); - } -} - -// Common implementation of process() and fill() - -template -void apply_common(bool create, - F f, - int b0, // = INT_MIN - int b1, // = INT_MAX - sample_index_t st0, // = INT64_MIN - sample_index_t st1, // = INT64_MAX - coefs &coefs0, - coefs&... coefsi) -{ - apply_to_slice(create, - [&](int bno, int64_t st, int time_step, - unsigned len, C0 *p0, CI *...pi) - { - for (unsigned int i = 0; i < len; i++) { - f(bno, st, *p0++, *pi++...); - st += time_step; - } - }, b0, b1, st0, st1, coefs0, coefsi...); -} - -// Iterate over one or more coefficient sets in parallel and apply the -// function f, passing it a coefficient from each set as an argument. -// -// The application can be optionally limited to coefficients within -// the band range b0 to b1 and/or the sample time range st0 to st1. -// -// The first coefficient set ("channel 0") is treated specially; it -// determines which coefficients are iterated over (optionally further -// restricted by b0/b1/st0/st1). That is, the iteration is over the -// coefficients that already exist in channel 0, and no new -// coefficients will be allocated in channel 0. In the other -// channels, new coefficients will be created on demand when they are -// missing from that channel but present in channel 0. -// -// The coefficients may be of a different data type in each set. -// -// The arguments to f() are: -// -// int bno Band number -// int64_t st Sample time -// C &p0 Coefficient from first set -// C... &pi Coefficient from subsequent set - -template -void process(F f, - int b0, // = INT_MIN - int b1, // = INT_MAX - sample_index_t t0, // = INT64_MIN - sample_index_t t1, // = INT64_MAX - coefs &coefs0, - coefs&... coefsi) -{ - apply_common(false, f, b0, b1, t0, t1, coefs0, coefsi...); -} - -template -void fill(F f, - int b0, // = INT_MIN - int b1, // = INT_MAX - sample_index_t t0, // = INT64_MIN - sample_index_t t1, // = INT64_MAX - coefs &coefs0, - coefs&... coefsi) -{ - apply_common(true, f, b0, b1, t0, t1, coefs0, coefsi...); -} - -// Apply the function f to each existing coefficient in the -// coefficient set msc within the time range st0 to st1. The -// initial analyzer argument is ignored. -// -// The arguments to f() are: -// -// C &c Coefficient -// int b Band number -// int64_t t Time in samples -// -// This is for backwards compatibility; process() is now preferred. - -template -void apply(const analyzer &, coefs &msc, F f, - sample_index_t st0 = INT64_MIN, - sample_index_t st1 = INT64_MAX) -{ - process([&](int b, int64_t t, complex& c) { - f(c, b, t); - }, INT_MIN, INT_MAX, st0, st1, msc); -} - -template -void forget_before(const analyzer &, coefs &msc, - sample_index_t limit, bool clean_cut = false) -{ - typedef complex C; - unsigned int n_oct = (unsigned int) msc.octaves.size(); - for (unsigned int oct = 0; oct < n_oct; oct++) { - sliced_coefs &sc = msc.octaves[oct]; - // Convert limit from samples to slices, rounding down. - // This assumes all bands in the octave have the same - // time range, as they must. - // First convert samples to coefficients, rounding down - int obno = 0; // Any band would do, and band 0 always exists - zone_coefs_meta *zmeta = msc.meta->octaves[oct].z; - coef_index_t ci = limit >> band_scale_exp(*zmeta, oct, obno); - // Then convert coefficients to slices, accounting for - // fat and rounding down - unsigned int slice_len = zmeta->bands[obno].slice_len; - unsigned int slice_len_log2 = zmeta->bands[obno].slice_len_log2; - int fat = slice_len >> 1; - slice_index_t sli = (ci - fat) >> slice_len_log2; - sc.slices.erase_before(sli); - if (clean_cut) { - // Partially erase slice at boundary, if any - const ref> *t = sc.slices.get(sli); - if (! t) - continue; - if (! *t) - continue; - const oct_coefs &c = **t; - unsigned int n_bands = (unsigned int)c.bands.size(); - for (unsigned int obno = 0; obno < n_bands; obno++) { - C *band = c.bands[obno]; - unsigned int len = sc.meta->bands[obno].slice_len; - sample_index_t st = sample_time(*sc.meta, sli, 0, oct, obno); - int time_step = 1 << band_scale_exp(*sc.meta, oct, obno); - for (unsigned int i = 0; i < len; i++) { - if (st < limit) - band[i] = 0; - else - break; - st += time_step; - } - } - } - } -} - - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/gaussian.h b/lib/gaborator/gaborator/gaussian.h deleted file mode 100644 index 1ee76b3..0000000 --- a/lib/gaborator/gaborator/gaussian.h +++ /dev/null @@ -1,123 +0,0 @@ -// -// The Gaussian and related functions -// -// Copyright (C) 2015-2021 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 - -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_area_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_area_support_inv(double support, double max_error) { - return support / (M_SQRT2 * erfc_inv(max_error)); -} - -// Given a gaussian with standard deviation "sd" and a maximum error -// "max_error", calculate the support needed on each side for the -// value to fall to a factor of "max_error" of the peak. - -static inline -double gaussian_value_support(double sd, double max_error) { - return sd * M_SQRT2 * sqrt(-log(max_error)); -} - -// Inverse of the above: given a support and maximum error, calculate -// the standard deviation. - -static inline -double gaussian_value_support_inv(double support, double max_error) { - return support / (M_SQRT2 * sqrt(-log(max_error))); -} - -// Choose which criterion to use - -#if 1 -static inline -double gaussian_support(double support, double max_error) { - return gaussian_area_support(support, max_error); -}; - -static inline -double gaussian_support_inv(double support, double max_error) { - return gaussian_area_support_inv(support, max_error); -}; -#else -static inline -double gaussian_support(double support, double max_error) { - return gaussian_value_support(support, max_error); -}; - -static inline -double gaussian_support_inv(double support, double max_error) { - return gaussian_value_support_inv(support, max_error); -}; -#endif - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/pod_vector.h b/lib/gaborator/gaborator/pod_vector.h deleted file mode 100644 index e578864..0000000 --- a/lib/gaborator/gaborator/pod_vector.h +++ /dev/null @@ -1,116 +0,0 @@ -// -// A vector class without default-initialization, for "plain old data" -// -// Copyright (C) 2016-2021 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 // size_t - -#include // std::swap - -namespace gaborator { - -// A vector for storing Plain Old Data. This is similar to a -// std::vector, except that it does not zero-initialize elements, -// and that it guarantees that data() returns a non-NULL pointer -// even when the vector contains zero elements. - -template -struct pod_vector { - typedef T value_type; - typedef T *iterator; - pod_vector() { - b = e = 0; - } - explicit pod_vector(size_t size_) { - // Allocate raw uninitialized memory - b = static_cast(::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(::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 = static_cast(::operator new(a.size() * sizeof(T))); - 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; - _free(); - b = x.b; - e = x.e; - x.b = x.e = 0; - return *this; - } -#endif -private: - void _free() { - // Free as raw uninitialized memory - ::operator delete(b); - } -private: - T *b; - T *e; -}; - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/pool.h b/lib/gaborator/gaborator/pool.h deleted file mode 100644 index 47e8322..0000000 --- a/lib/gaborator/gaborator/pool.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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 - -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 -struct pool { - typedef std::map 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 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 -pool pool::shared; - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/ref.h b/lib/gaborator/gaborator/ref.h deleted file mode 100644 index fb4562f..0000000 --- a/lib/gaborator/gaborator/ref.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// 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 struct ref; - -struct refcounted { - refcounted() { refcount = 0; } - unsigned int refcount; -}; - -// Template functions for manual reference counting, without using the -// ref<> class. It would be tempting to make these methods of struct -// refcounted, but that won't work because it would lose the full -// object type and invoke operator delete on the base class. - -template -void incref(T &r) { - r.refcount++; -} - -template -void decref(T &r) { - r.refcount--; - if (r.refcount == 0) - delete &r; -} - -template -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; - incref(*p); - } - void _decref() { - if (! p) - return; - decref(*p); - } - T *p; -}; - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/render.h b/lib/gaborator/gaborator/render.h deleted file mode 100644 index 2b8df98..0000000 --- a/lib/gaborator/gaborator/render.h +++ /dev/null @@ -1,506 +0,0 @@ -// -// Rendering of spectrogram images -// -// Copyright (C) 2015-2021 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 -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); -} - -////////////////////////////////////////////////////////////////////////////// - -// Power-of-two rendering - - -// Magnitude - -template -struct complex_abs_fob { - T operator()(const complex &c) { - return complex_abs(c); - } - typedef T return_type; -}; - - -// T -> f() -> OI::value_type - -template -struct transform_output_iterator: public std::iterator { - typedef T value_type; - transform_output_iterator(F f_, OI output_): f(f_), output(output_) { } - transform_output_iterator& operator=(T v) { - *output++ = f(v); - return *this; - } - transform_output_iterator& operator*() { return *this; } - transform_output_iterator& operator++() { return *this; } - transform_output_iterator& operator++(int) { return *this; } - F f; - OI output; -}; - -// A source object for resample2() that provides the -// values of a row of spectrogram coordinates transformed -// by function normf (typically an absolute value function). -// -// T is the analyzer signal type -// C is the coefficient type -// OI is the output iterator type - -template -struct abs_row_source { - typedef transform_output_iterator abs_writer_t; - abs_row_source(const coefs &coefs_, - int oct_, unsigned int obno_, - NORMF normf_): - rs(coefs_, 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 rs; - NORMF normf; -}; - -// Helper class for abs_row_source specialization below - -template -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 -struct abs_row_source> { - // Note unused last arg - abs_row_source(const coefs &coefs_, - int oct_, unsigned int obno_, - complex_abs_fob): - slicer(coefs_, oct_, obno_) - { } - OI operator()(coef_index_t i0, coef_index_t i1, OI output) const { - abs_writer_dest dest(output); - slicer(i0, i1, dest); - return dest.output; - } - row_foreach_slice, C> slicer; -}; - -// Render a single line (single frequency band), with scaling in the -// horizontal (time) dimension, and filtering to avoid aliasing when -// minifying. - -template -OI render_line(const analyzer &anl, - const coefs &msc, - int gbno, - affine_transform xf, - sample_index_t i0, sample_index_t i1, - OI output, - NORMF normf) -{ - typedef typename NORMF::return_type RST; - - int oct; - unsigned int obno; // Band number within octave - bool clip = ! bno_split(*msc.meta, gbno, oct, obno, false); - if (clip) { - for (sample_index_t i = i0; i < i1; i++) - *output++ = (T)0; - return output; - } - abs_row_source - abs_rowsource(msc, oct, obno, normf); - - // Scale by the downsampling factor of the band - int scale_exp = band_scale_exp(*msc.octaves[oct].meta, oct, obno); - RESAMPLER x_resampler(zoom_p2(xf, -scale_exp)); - output = x_resampler(abs_rowsource, i0, i1, output); - return output; -} - -// Render a two-dimensional image with scaling 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 -OI render_noyscale(const analyzer &anl, - const coefs &msc, - affine_transform x_xf, - int64_t xi0, int64_t xi1, - int64_t yi0, int64_t yi1, - OI output, - int64_t output_stride, - NORMF normf) -{ - assert(xi1 >= xi0); - int w = (int)(xi1 - xi0); - assert(w >= 0); - int gbno0 = (int)yi0; - int gbno1 = (int)yi1; - for (int gbno = gbno0; gbno < gbno1; gbno++) { - render_line - (anl, msc, gbno, x_xf, - xi0, xi1, - output, normf); - output += output_stride; - } - 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 -struct transverse_source { - transverse_source(T *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); - T *p = data + (x - x0) + (i0 - y0) * stride; - while (i0 != i1) { - *out++ = *p; - p += stride; - ++i0; - } - return out; - } - T *data; - int64_t x0, x1, y0, y1, x; - size_t stride; -}; - -template -struct stride_iterator: public std::iterator - ::value_type> -{ - typedef typename std::iterator_traits::value_type T; - stride_iterator(I it_, size_t stride_): it(it_), stride(stride_) { } - T& operator*() { return *it; } - stride_iterator& operator++() { - it += stride; - return *this; - } - stride_iterator operator++(int) { - stride_iterator old = *this; - it += stride; - return old; - } - I it; - size_t stride; -}; - -struct updated_nop { - void operator()(int64_t x0, int64_t x1, int64_t y0, int64_t y1) { } -}; - -// Render a two-dimensional image with scaling 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()" -// 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. - -// This supports incremental rendering where only the parts of the -// output image affected by analyzing a given time range of samples -// are updated, the time range being from inc_i0 (inclusive) to inc_i1 -// (exclusive). The updated parts of the image consist of zero or -// more non-overlapping rectangles; to find out what those are, pass a -// function "update" which will be called will each rectangle in turn -// after it has been updated. - -// For non-incremental rendering, pass inc_i0 = INT64_MIN and inc_i1 = -// INT64_MAX. - -// OI is the output iterator type -// T is the coefficient type - -template , - class RESAMPLER = lanczos2_pow2_resampler, - class UPDATEDF = updated_nop> -void render_incremental( - const analyzer &anl, - const coefs &msc, - affine_transform x_xf, affine_transform y_xf, - int64_t xi0, int64_t xi1, - int64_t yi0, int64_t yi1, - int64_t inc_i0, int64_t inc_i1, - OI output, - int64_t output_stride, - NORMF normf = complex_abs_fob(), - UPDATEDF updated = updated_nop()) -{ - assert(xi1 >= xi0); - assert(yi1 >= yi0); - assert(inc_i1 >= inc_i0); - - // The data type to reasmple - typedef typename NORMF::return_type RST; - - // Vertical resampler - RESAMPLER y_resampler(y_xf); - - // 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 - // coefficient samples, and are only used for short-circuiting. - // The X bounds will be calculated later if we need them. - int64_t ysi0, ysi1; - y_resampler.support(yi0, yi1, ysi0, ysi1); - - // Calculate adjusted image X bounds based on the updated signal - // range for incremental rendering, and return an estimate of the - // numbers of pixels we can avoid rendering at this Y coordinate - // by using the adjusted X bounds. - - auto savings = [&](int64_t y, int64_t &adj_x0, int64_t &adj_x1) { - // Find the highest-index / lowest-frequency band used - // as a resampler input for output pixel y; it will have - // the widest analysis support in the x direction. - // Note that we pass y twice, and ignore the ysi0 result. - int64_t ysi0, ysi1; - y_resampler.support(y, y, ysi0, ysi1); - int64_t band = ysi1; - // Clamp the band to the valid range - band = std::max(band, (int64_t)anl.bandpass_bands_begin()); - band = std::min(band, (int64_t)anl.bandpass_bands_end() - 1); - - // Find the analysis support in the time (x) dimension, - // in signal samples - double support = anl.analysis_support(band); - // Convert from signal samples to coefficient samples - int scale_exp = anl.band_scale_exp((int)band); - - // Extend the updated coefficient range by the analysis - // support, and map it back to pixel space to find the - // affected pixel range, taking the resampler support - // into account. - RESAMPLER x_resampler(zoom_p2(x_xf, -scale_exp)); - int64_t ceil_support = ceil(support); - - // inv_support() calculates both sides of the support at once, - // but in the one-sided case, passing INT64_MIN/MAX may cause - // overflow and undefined behavior. Therefore, we pass a - // dummy value of zero instead, and make sure not to use the - // corresponding output value. This may cause the two inputs - // to inv_support() to be out of order, so it needs to accept - // that. - x_resampler.inv_support( - inc_i0 == INT64_MIN ? (int64_t)0 : (inc_i0 - ceil_support) >> scale_exp, - inc_i1 == INT64_MAX ? (int64_t)0 : (inc_i1 + ceil_support) >> scale_exp, - adj_x0, - adj_x1); - - if (inc_i0 == INT64_MIN) { - adj_x0 = xi0; - } else { - adj_x0 = std::max(xi0, adj_x0); - // Don't let width go negative - adj_x0 = std::min(adj_x0, xi1); - } - - if (inc_i1 == INT64_MAX) { - adj_x1 = xi1; - } else { - adj_x1 = std::min(xi1, adj_x1); - // Don't let width go negative - adj_x1 = std::max(adj_x1, adj_x0); - } - - assert(adj_x0 <= adj_x1); - - return (adj_x0 - xi0) + (xi1 - adj_x1); - }; - - if (!(inc_i0 == INT64_MIN && inc_i1 == INT64_MAX)) { - // Incremental rendering has been requested - int64_t adj_x0_top, adj_x1_top, adj_x0_bot, adj_x1_bot; - // See how much rendering we can save per line at the bottom, - // and calcualate adjusted bounds - int64_t bot_savings = savings(ysi1, adj_x0_bot, adj_x1_bot); - // See how much rendering we can save per line at the top - int64_t top_savings = savings(ysi0, adj_x0_top, adj_x1_top); - // Adjust bounds and output pointer to realize the bottom - // savings - if (adj_x0_bot == adj_x1_bot) - return; - output += adj_x0_bot - xi0; - xi0 = adj_x0_bot; - xi1 = adj_x1_bot; - - // If the savings at the top are significantly greater than - // at the bottom, it pays to subdivde the area to render, - // so that the top part can benefit from the greater savings - // there. - if (((top_savings - bot_savings) * (yi1 - yi0)) > 1000) { - // Subdivide vertically - int64_t ysplit = (yi1 + yi0) >> 1; - size_t output_offset = (ysplit - yi0) * output_stride; - render_incremental - (anl, msc, x_xf, y_xf, - xi0, xi1, - yi0, ysplit, - inc_i0, inc_i1, - output, output_stride, normf, updated); - render_incremental - (anl, msc, x_xf, y_xf, - xi0, xi1, - ysplit, yi1, - inc_i0, inc_i1, - output + output_offset, output_stride, normf, updated); - return; - } - } - - // Horizontal resampler, used only to calculate the support for - // short-circuiting. Since the resampling factor varies by band, - // the support also varies; use the largest resampling factor of - // any band to get the worst-case support. - int worstcase_band = anl.bandpass_bands_end() - 1; - RESAMPLER x_resampler(zoom_p2(x_xf, -anl.band_scale_exp(worstcase_band))); - int64_t xsi0, xsi1; - x_resampler.support(xi0, xi1, 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 to keep - // the total amount of work bounded by O(L) with respect to the - // signal length L regardless of zoom. - coef_index_t cxi0, cxi1; - get_band_coef_bounds(msc, worstcase_band, cxi0, cxi1); - if (ysi1 < 0 || // Entirely above - ysi0 >= anl.n_bands_total - 1 || // Entirely below - xsi1 < cxi0 || // Entirely to the left - xsi0 >= cxi1) // Entirely to the right - { - size_t n = (size_t)((yi1 - yi0) * (xi1 - xi0)); - for (size_t i = 0; i < n; i++) - output[i] = (T)0; - return; - } - - if (y_xf.a == 1 && y_xf.b == 0) { - // No Y resampling needed, render data resampled in the X - // direction only. - render_noyscale - (anl, msc, x_xf, xi0, xi1, yi0, yi1, - output, output_stride, normf); - } else { - // Construct a temporary image resampled in the - // X direction, but not yet in the Y direction. Include - // extra scanlines at the top and bottom for interpolation. - size_t n_pixels = (size_t)((ysi1 - ysi0) * (xi1 - xi0)); - pod_vector render_data(n_pixels); - - // Render data resampled in the X direction - RST *p = render_data.data(); - render_noyscale - (anl, msc, x_xf, xi0, xi1, - ysi0, ysi1, p, xi1 - xi0, normf); - - // Resample in the Y direction - for (int64_t xi = xi0; xi < xi1; xi++) { - transverse_source src(render_data.data(), - xi0, xi1, ysi0, ysi1, - xi); - stride_iterator dest(output + (xi - xi0), output_stride); - y_resampler(src, yi0, yi1, dest); - } - } - updated(xi0, xi1, yi0, yi1); -} - -template , - class RESAMPLER = lanczos2_pow2_resampler> -void render_p2scale(const analyzer &anl, - const coefs &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, - NORMF normf = complex_abs_fob()) -{ - // Provide default inc_i0, inc_i1, and output_stride - render_incremental - (anl, msc, - affine_transform(ldexp(1, xe), xorigin), - affine_transform(ldexp(1, ye), yorigin), - xi0, xi1, yi0, yi1, - INT64_MIN, INT64_MAX, - output, xi1 - xi0, normf); -} - - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/resample2.h b/lib/gaborator/gaborator/resample2.h deleted file mode 100644 index c8b6e08..0000000 --- a/lib/gaborator/gaborator/resample2.h +++ /dev/null @@ -1,324 +0,0 @@ -// -// Fast resampling by powers of two -// -// Copyright (C) 2016-2021 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 -#include -#include -#include // std::copy - -#include "gaborator/affine_transform.h" -#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. -// - -// A power-of-two transform, as in y = 2^e x + origin - -struct p2_transform { - p2_transform(int e_, int64_t origin_): e(e_), origin(origin_) { } - // Convert a linear transform into a p2_transform - p2_transform(affine_transform xf) { - int exp; - double m = frexp(xf.a, &exp); - assert(m == 0.5); - e = exp - 1; - origin = xf.b; - assert(origin == xf.b); // No fraction - } - int e; - int64_t origin; -}; - -// Scale a transform by a power of two - -static inline p2_transform -zoom_p2(p2_transform xf, int e) { - return p2_transform(xf.e + e, xf.origin); -} - -static inline affine_transform -zoom_p2(affine_transform xf, int e) { - return affine_transform(ldexp(xf.a, e), xf.b); -} - -// 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 -OI resample2_p2xf(const S &source, p2_transform xf, - int64_t i0, int64_t i1, - bool interpolate, OI out) -{ - typedef typename std::iterator_traits::value_type T; - assert(i1 >= i0); - if (xf.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 super_data(si1 - si0); - T *p = super_data.data(); - p = resample2_p2xf(source, p2_transform(xf.e - 1, xf.origin), - si0, si1, 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 (xf.e < 0) { - // Upsample - if (! interpolate) { - // Return nearest neighbor. If the pixel lies - // exactly at the midpoint between the neighbors, - // return their average. - int sh = -xf.e; - int64_t si0 = i0 >> sh; - int64_t si1 = ((i1 - 1) >> sh) + 1 + 1; - gaborator::pod_vector source_data(si1 - si0); - source(xf.origin + si0, xf.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 sub_data(si1 - si0); - T *p = sub_data.data(); - p = resample2_p2xf(source, p2_transform(xf.e + 1, xf.origin), - si0, si1, 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(xf.origin + i0, xf.origin + i1, out); - } - return out; -} - -// As above, but taking an affine_transform. - -template -OI resample2(const S &source, affine_transform lxf, - int64_t i0, int64_t i1, - bool interpolate, OI out) -{ - p2_transform xf(lxf); - typedef typename std::iterator_traits::value_type T; - gaborator::pod_vector data(i1 - i0); - T *p = data.data(); - p = resample2_p2xf(source, xf, i0, i1, 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(affine_transform lxf, int64_t i0, int64_t i1, - int64_t &si0_ret, int64_t &si1_ret) -{ - p2_transform xf(lxf); - int margin = 2; - if (xf.e > 0) { - // Note code duplication wrt resample2_p2xf(). - // Also note tail recursion. - int64_t si0 = i0 * 2 - margin + 1; - int64_t si1 = i1 * 2 + margin; - resample2_support(zoom_p2(lxf, -1), - si0, si1, si0_ret, si1_ret); - } else if (xf.e < 0) { - int64_t si0 = (i0 >> 1) - margin; - int64_t si1 = ((i1 - 1) >> 1) + margin + 1; - resample2_support(zoom_p2(lxf, +1), - si0, si1, si0_ret, si1_ret); - } else { - si0_ret = xf.origin + i0; - si1_ret = xf.origin + i1; - } -} - -// Inverse of the above, more or less: calculate the range of -// destination indices that depend on a given range of source indices. - -inline void -resample2_inv_support(affine_transform lxf, int64_t si0, int64_t si1, - int64_t &i0_ret, int64_t &i1_ret) -{ - p2_transform xf(lxf); - // Conservative - int margin = 2; - if (xf.e > 0) { - int64_t di0, di1; - resample2_inv_support(zoom_p2(lxf, -1), - si0, si1, di0, di1); - i0_ret = di0 >> 1; - i1_ret = (di1 >> 1) + 1; - } else if (xf.e < 0) { - int64_t di0, di1; - resample2_inv_support(zoom_p2(lxf, +1), - si0, si1, di0, di1); - i0_ret = di0 * 2 - margin + 1; - i1_ret = di1 * 2 + margin; - } else { - i0_ret = si0 - xf.origin; - i1_ret = si1 - xf.origin; - } -} - -// Class wrappers for compatibility with other resamplers. - -// Lanczos2 power-of-two resampler - -struct lanczos2_pow2_resampler { - lanczos2_pow2_resampler(affine_transform xf_): xf(xf_) { } - template - OI operator()(const S &source, int64_t i0, int64_t i1, OI out) const { - return resample2(source, xf, i0, i1, true, out); - } - void support(int64_t i0, int64_t i1, int64_t &si0_ret, int64_t &si1_ret) const { - return resample2_support(xf, i0, i1, si0_ret, si1_ret); - } - void inv_support(int64_t si0, int64_t si1, int64_t &i0_ret, int64_t &i1_ret) { - return resample2_inv_support(xf, si0, si1, i0_ret, i1_ret); - } - affine_transform xf; -}; - -// Nearest-neighbor power-of-two resampler -// XXX simplify - -struct nn_pow2_resampler { - nn_pow2_resampler(affine_transform xf_): xf(xf_){ } - template - OI operator()(const S &source, int64_t i0, int64_t i1, OI out) const { - return resample2(source, xf, i0, i1, false, out); - } - void support(int64_t i0, int64_t i1, int64_t &si0_ret, int64_t &si1_ret) const { - return resample2_support(xf, i0, i1, si0_ret, si1_ret); - } - void inv_support(int64_t si0, int64_t si1, int64_t &i0_ret, int64_t &i1_ret) { - return resample2_inv_support(xf, si0, si1, i0_ret, i1_ret); - } - affine_transform xf; -}; - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/vector_math.h b/lib/gaborator/gaborator/vector_math.h deleted file mode 100644 index f1d9cca..0000000 --- a/lib/gaborator/gaborator/vector_math.h +++ /dev/null @@ -1,301 +0,0 @@ -// -// 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 - -#if GABORATOR_USE_SSE3_INTRINSICS -#include -#endif - -#include - -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 -std::complex complex_mul_naive(std::complex a, - std::complex b) -{ - return std::complex(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 complex_mul(std::complex a_, - std::complex 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(c[0], c[1]); -} -#else -static inline -std::complex complex_mul(std::complex a_, - std::complex b_) -{ - return complex_mul_naive(a_, b_); -} -#endif - -static inline -std::complex complex_mul(std::complex a_, - std::complex b_) -{ - return complex_mul_naive(a_, b_); -} - -template -static inline void -elementwise_product_naive(T *r, - U *a, - V *b, - size_t n) -{ - for (size_t i = 0; i < n; i++) - r[i] = complex_mul(a[i], b[i]); -} - -template -static inline void -elementwise_product_times_scalar_naive(T *r, - U *a, - V *b, - S s, - size_t n) -{ - for (size_t 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 -static inline void -complex_magnitude_naive(I *inv, - O *outv, - size_t n) -{ - for (size_t 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 - -// 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 *cv, - const std::complex *av, - const std::complex *bv, - size_t 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 *cv, - const std::complex *av, - const float *bv, - size_t 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 *cv, - const std::complex *av, - const std::complex *bv, - std::complex d, - size_t 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 *inv, - float *outv, - size_t 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 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 *) in; - outv = (float *)out; - while (n) { - std::complex 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 *c, - const std::complex *a, - const std::complex *b, - size_t n) -{ - elementwise_product_naive(c, a, b, n); -} - -static inline void -elementwise_product(std::complex *c, - const std::complex *a, - const double *b, - size_t n) -{ - elementwise_product_naive(c, a, b, n); -} - -template -static inline void -elementwise_product_times_scalar(T *r, - U *a, - V *b, - S s, - size_t n) -{ - elementwise_product_times_scalar_naive(r, a, b, s, n); -} - -template -static inline void -complex_magnitude(std::complex *inv, - O *outv, - size_t 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 -static inline void -elementwise_product(T *r, - U *a, - V *b, - size_t n) -{ - elementwise_product_naive(r, a, b, n); -} - -template -static inline void -elementwise_product_times_scalar(T *r, - U *a, - V *b, - S s, - size_t n) -{ - elementwise_product_times_scalar_naive(r, a, b, s, n); -} - -template -static inline void -complex_magnitude(I *inv, - O *outv, - size_t n) -{ - complex_magnitude_naive(inv, outv, n); -} - -#endif // ! USE_SSE3_INTINSICS - -} // namespace - -#endif diff --git a/lib/gaborator/gaborator/version.h b/lib/gaborator/gaborator/version.h deleted file mode 100644 index 41ddc8a..0000000 --- a/lib/gaborator/gaborator/version.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Version number -// -// Copyright (C) 2015-2021 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_VERSION_H -#define _GABORATOR_VERSION_H - -#define GABORATOR_VERSION_MAJOR 1 -#define GABORATOR_VERSION_MINOR 7 - -#endif