Mercurial > hg > tuning-difference
changeset 39:0cca2cf913ae
Add constant-q-cpp code
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/.hgignore Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,12 @@ +syntax: glob +*~ +*.o +*.so +*.a +*.dylib +*.dll +*.class +doc/ + +syntax: re +^test/Test[^.]*$
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/.hgtags Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,4 @@ +361b4f8b7b2dbce9aebec3ff577f5c68f6fbb3ae v1.0 +361b4f8b7b2dbce9aebec3ff577f5c68f6fbb3ae v1.0 +5a4ece568299eba6ca7e54b283de5e98b6a3526b v1.0 +50a61e18af29ab383f210c1f2099b4dc49f1495f v1.1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/CITATION Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,7 @@ + @inproceedings{christian2010a, + author = {Christian Schörkhuber and Anssi Klapuri}, + title = {Constant-Q transform toolbox for music processing}, + booktitle = {Proceedings of the 7th Sound and Music Computing Conference, Barcelona, Spain}, + month = {July}, + year = {2010} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/COPYING Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,65 @@ + +This library is distributed under the following licence: + + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. + +The code in src/ext/kissfft is distributed under the following licence: + + Copyright (c) 2003-2010 Mark Borgerding + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the author nor the names of any contributors may be used + to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/Doxyfile Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,2310 @@ +# Doxyfile 1.8.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Constant-Q Library" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = cq src vamp + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. To get the times font for +# instance you can specify +# EXTRA_PACKAGES=times +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will +# replace them by respectively the title of the page, the current date and time, +# only the current date, the version number of doxygen, the project name (see +# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen +# Definitions (see http://autogen.sf.net) file that captures the structure of +# the code including all documentation. Note that this feature is still +# experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names +# in the source code. If set to NO only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external class will be listed in the +# class index. If set to NO only the inherited external classes will be listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in +# the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font n the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif and svg. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/Makefile.inc Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,182 @@ + +LIB_DIR := src +INC_DIR := cq +TEST_DIR := test +VAMP_DIR := vamp +KFFT_DIR := src/ext/kissfft + +VAMPSDK_DIR ?= ../vamp-plugin-sdk + +PLUGIN_EXT ?= .so + +CXX ?= g++ +CC ?= gcc +AR ?= ar +RANLIB ?= ranlib + +#VALGRIND ?= valgrind -q + +GENERAL_FLAGS := -I. -I$(VAMPSDK_DIR) -I$(INC_DIR) -I$(LIB_DIR) -I$(KFFT_DIR) -I$(KFFT_DIR)/tools -Dkiss_fft_scalar=double + +CFLAGS := $(GENERAL_FLAGS) $(CFLAGS) +CXXFLAGS := $(GENERAL_FLAGS) $(CXXFLAGS) + +LDFLAGS := $(LDFLAGS) +PLUGIN_LDFLAGS := $(LDFLAGS) $(PLUGIN_LDFLAGS) +TEST_LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework +PF_LDFLAGS := $(LDFLAGS) -lsndfile + +LIB := libcq.a +PLUGIN := cqvamp$(PLUGIN_EXT) +PF := $(TEST_DIR)/processfile + +LIB_HEADERS := \ + $(INC_DIR)/CQBase.h \ + $(INC_DIR)/CQKernel.h \ + $(INC_DIR)/ConstantQ.h \ + $(INC_DIR)/CQSpectrogram.h \ + $(INC_DIR)/CQInverse.h \ + $(INC_DIR)/Chromagram.h \ + $(LIB_DIR)/Pitch.h \ + $(LIB_DIR)/dsp/FFT.h \ + $(LIB_DIR)/dsp/KaiserWindow.h \ + $(LIB_DIR)/dsp/MathUtilities.h \ + $(LIB_DIR)/dsp/nan-inf.h \ + $(LIB_DIR)/dsp/Resampler.h \ + $(LIB_DIR)/dsp/SincWindow.h \ + $(LIB_DIR)/dsp/Window.h \ + $(KFFT_DIR)/kiss_fft.h \ + $(KFFT_DIR)/tools/kiss_fftr.h + +LIB_SOURCES := \ + $(LIB_DIR)/CQKernel.cpp \ + $(LIB_DIR)/ConstantQ.cpp \ + $(LIB_DIR)/CQSpectrogram.cpp \ + $(LIB_DIR)/CQInverse.cpp \ + $(LIB_DIR)/Chromagram.cpp \ + $(LIB_DIR)/Pitch.cpp \ + $(LIB_DIR)/dsp/FFT.cpp \ + $(LIB_DIR)/dsp/KaiserWindow.cpp \ + $(LIB_DIR)/dsp/MathUtilities.cpp \ + $(LIB_DIR)/dsp/Resampler.cpp \ + $(LIB_DIR)/dsp/SincWindow.cpp \ + $(KFFT_DIR)/kiss_fft.c \ + $(KFFT_DIR)/tools/kiss_fftr.c + +VAMP_HEADERS := \ + $(VAMP_DIR)/CQVamp.h \ + $(VAMP_DIR)/CQChromaVamp.h + +VAMP_SOURCES := \ + $(VAMP_DIR)/CQVamp.cpp \ + $(VAMP_DIR)/CQChromaVamp.cpp \ + $(VAMP_DIR)/libmain.cpp + +TEST_SOURCES := \ + $(TEST_DIR)/TestFFT.cpp \ + $(TEST_DIR)/TestMathUtilities.cpp \ + $(TEST_DIR)/TestResampler.cpp \ + $(TEST_DIR)/TestWindow.cpp \ + $(TEST_DIR)/TestCQKernel.cpp \ + $(TEST_DIR)/TestCQFrequency.cpp \ + $(TEST_DIR)/TestCQTime.cpp + +HEADERS := $(LIB_HEADERS) $(VAMP_HEADERS) +SOURCES := $(LIB_SOURCES) $(VAMP_SOURCES) + +LIB_OBJECTS := $(LIB_SOURCES:.cpp=.o) +LIB_OBJECTS := $(LIB_OBJECTS:.c=.o) + +OBJECTS := $(SOURCES:.cpp=.o) +OBJECTS := $(OBJECTS:.c=.o) + +TEST_OBJECTS := $(TEST_SOURCES:.cpp=.o) +TEST_TARGETS := $(TEST_SOURCES:.cpp=) + +PF_SOURCES := $(TEST_DIR)/processfile.cpp +PF_OBJECTS := $(PF_SOURCES:.cpp=.o) $(OBJECTS) + +LIBS := $(VAMPSDK_DIR)/libvamp-sdk.a -lpthread + +default: all + +all: libs $(PF) test + +libs: $(LIB) $(PLUGIN) + +test: libs $(TEST_TARGETS) + for t in $(TEST_TARGETS); do echo; echo "Running $$t"; $(VALGRIND) ./"$$t" || exit 1; done && echo && $(VALGRIND) "./test/test-inverse.sh" && echo 'Tests complete' + +$(PLUGIN): $(OBJECTS) + $(CXX) -o $@ $^ $(LIBS) $(PLUGIN_LDFLAGS) + +$(PF): $(PF_OBJECTS) + $(CXX) -o $@ $^ $(LIBS) $(PF_LDFLAGS) + +$(LIB): $(LIB_OBJECTS) + $(RM) -f $@ + $(AR) cr $@ $^ + $(RANLIB) $@ + +$(TEST_DIR)/Test%: $(TEST_DIR)/Test%.o + $(CXX) -o $@ $^ $(LIB) $(LIBS) $(TEST_LDFLAGS) + +clean: + rm -f $(OBJECTS) $(TEST_OBJECTS) $(PF_OBJECTS) + +distclean: clean + rm -f $(PLUGIN) $(TEST_TARGETS) + +depend: + makedepend -Y -fMakefile.inc $(SOURCES) $(TEST_SOURCES) $(PF_SOURCES) $(HEADERS) + +# DO NOT DELETE + +src/CQKernel.o: src/dsp/MathUtilities.h src/dsp/nan-inf.h src/dsp/FFT.h +src/CQKernel.o: src/dsp/Window.h +src/ConstantQ.o: src/dsp/Resampler.h src/dsp/MathUtilities.h +src/ConstantQ.o: src/dsp/nan-inf.h src/dsp/FFT.h +src/CQInverse.o: src/dsp/Resampler.h src/dsp/MathUtilities.h +src/CQInverse.o: src/dsp/nan-inf.h src/dsp/FFT.h +src/Chromagram.o: src/Pitch.h +src/Pitch.o: src/Pitch.h +src/dsp/FFT.o: src/dsp/FFT.h src/dsp/MathUtilities.h src/dsp/nan-inf.h +src/dsp/KaiserWindow.o: src/dsp/KaiserWindow.h src/dsp/MathUtilities.h +src/dsp/KaiserWindow.o: src/dsp/nan-inf.h +src/dsp/MathUtilities.o: src/dsp/MathUtilities.h src/dsp/nan-inf.h +src/dsp/Resampler.o: src/dsp/Resampler.h src/dsp/MathUtilities.h +src/dsp/Resampler.o: src/dsp/nan-inf.h src/dsp/KaiserWindow.h +src/dsp/Resampler.o: src/dsp/SincWindow.h +src/dsp/SincWindow.o: src/dsp/SincWindow.h +src/ext/kissfft/kiss_fft.o: src/ext/kissfft/_kiss_fft_guts.h +src/ext/kissfft/kiss_fft.o: src/ext/kissfft/kiss_fft.h +src/ext/kissfft/tools/kiss_fftr.o: src/ext/kissfft/tools/kiss_fftr.h +src/ext/kissfft/tools/kiss_fftr.o: src/ext/kissfft/kiss_fft.h +src/ext/kissfft/tools/kiss_fftr.o: src/ext/kissfft/_kiss_fft_guts.h +vamp/CQVamp.o: vamp/CQVamp.h cq/CQSpectrogram.h cq/ConstantQ.h cq/CQBase.h +vamp/CQVamp.o: cq/CQParameters.h cq/CQKernel.h src/Pitch.h +vamp/CQChromaVamp.o: vamp/CQChromaVamp.h cq/CQSpectrogram.h cq/ConstantQ.h +vamp/CQChromaVamp.o: cq/CQBase.h cq/CQParameters.h cq/CQKernel.h +vamp/libmain.o: vamp/CQVamp.h cq/CQSpectrogram.h cq/ConstantQ.h cq/CQBase.h +vamp/libmain.o: cq/CQParameters.h cq/CQKernel.h vamp/CQChromaVamp.h +test/TestFFT.o: src/dsp/FFT.h +test/TestMathUtilities.o: src/dsp/MathUtilities.h src/dsp/nan-inf.h +test/TestResampler.o: src/dsp/Resampler.h src/dsp/Window.h src/dsp/FFT.h +test/TestWindow.o: src/dsp/Window.h +test/TestCQKernel.o: cq/CQKernel.h cq/CQParameters.h +test/TestCQFrequency.o: cq/CQSpectrogram.h cq/ConstantQ.h cq/CQBase.h +test/TestCQFrequency.o: cq/CQParameters.h cq/CQKernel.h src/dsp/Window.h +test/TestCQTime.o: cq/CQSpectrogram.h cq/ConstantQ.h cq/CQBase.h +test/TestCQTime.o: cq/CQParameters.h cq/CQKernel.h src/dsp/Window.h +test/processfile.o: cq/ConstantQ.h cq/CQBase.h cq/CQParameters.h +test/processfile.o: cq/CQKernel.h +cq/CQKernel.o: cq/CQParameters.h +cq/ConstantQ.o: cq/CQBase.h cq/CQParameters.h cq/CQKernel.h +cq/CQSpectrogram.o: cq/ConstantQ.h cq/CQBase.h cq/CQParameters.h +cq/CQSpectrogram.o: cq/CQKernel.h +cq/CQInverse.o: cq/CQBase.h cq/CQKernel.h cq/CQParameters.h +cq/Chromagram.o: cq/CQBase.h +src/dsp/MathUtilities.o: src/dsp/nan-inf.h +src/ext/kissfft/tools/kiss_fftr.o: src/ext/kissfft/kiss_fft.h +vamp/CQVamp.o: cq/CQSpectrogram.h cq/ConstantQ.h cq/CQBase.h +vamp/CQVamp.o: cq/CQParameters.h cq/CQKernel.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/Makefile.linux Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,15 @@ + +CFLAGS := -Wall -O3 -ffast-math -msse -msse2 -mfpmath=sse -fPIC -I../vamp-plugin-sdk/ + +#CFLAGS := -g -fPIC -I../vamp-plugin-sdk + +CXXFLAGS := $(CFLAGS) -std=c++11 + +PLUGIN_LDFLAGS := -shared -Wl,--version-script=vamp/vamp-plugin.map + +VAMPSDK_DIR := ../vamp-plugin-sdk + +PLUGIN_EXT := .so + +include Makefile.inc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/Makefile.mingw32 Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,19 @@ + +TOOLPREFIX ?= i586-mingw32msvc- +CXX = $(TOOLPREFIX)g++ +CC = $(TOOLPREFIX)gcc +LD = $(TOOLPREFIX)g++ +AR = $(TOOLPREFIX)ar +RANLIB = $(TOOLPREFIX)ranlib + +CFLAGS += -DNDEBUG -O3 -msse -msse2 -ffast-math -mfpmath=sse -Wall -Wextra +LDFLAGS += -static + +CXXFLAGS += $(CFLAGS) +PLUGIN_LDFLAGS := $(LDFLAGS) -shared -Wl,--retain-symbols-file=vamp/vamp-plugin.list + +VAMPSDK_DIR := ../vamp-plugin-sdk +PLUGIN_EXT := .dll + +include Makefile.inc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/Makefile.osx Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,15 @@ + +ARCHFLAGS ?= -mmacosx-version-min=10.7 -arch x86_64 -arch i386 -stdlib=libc++ + +CFLAGS := $(ARCHFLAGS) -Wall -O3 -ftree-vectorize +CXXFLAGS := $(CFLAGS) + +LDFLAGS := $(ARCHFLAGS) +PLUGIN_LDFLAGS := -dynamiclib -exported_symbols_list vamp/vamp-plugin.list + +VAMPSDK_DIR := ../vamp-plugin-sdk + +PLUGIN_EXT := .dylib + +include Makefile.inc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/README Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,48 @@ + +Constant-Q Library +================== + +A C++ library and Vamp plugin implementing the Constant-Q transform +of a time-domain signal. + +https://code.soundsoftware.ac.uk/projects/constant-q-cpp + +The Constant-Q transform is a time-to-frequency-domain transform +related to the short-time Fourier transform, but with output bins +spaced logarithmically in frequency, rather than linearly. The output +bins are therefore linearly spaced in terms of musical pitch. + +This library provides: + + * Forward transform: time-domain to complex Constant-Q bins + * Forward spectrogram: time-domain to interpolated Constant-Q + magnitude spectrogram + * Inverse transform: complex Constant-Q bins to time domain + +The Vamp plugin provides: + + * Constant-Q magnitude spectrogram with high and low frequency + extents defined in Hz + * Constant-Q magnitude spectrogram with high and low frequency + extents defined as MIDI pitch values + * Pitch chromagram obtained by folding a Constant-Q spectrogram + around into a single-octave range + +The method is drawn from Christian Schörkhuber and Anssi Klapuri, +"Constant-Q transform toolbox for music processing", SMC 2010. See the +file CITATION for details. If you use this code in research work, +please cite this paper. + +The implementation is reasonably fast and is causal, operating +block-by-block on the input, though with quite high latency +depending on the frequency range specified. By default it produces +output at a higher time resolution than some other implementations, +using multiple kernel atoms per time block. The inverse transform is +approximate rather than exact (see the paper for details). + +The C++ implementation is by Chris Cannam, Copyright 2014-2015 Queen +Mary, University of London. + +The library is provided under a liberal BSD/MIT-style open source +licence. See the file COPYING for more information. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/archive.sh Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,9 @@ +#!/bin/bash +tag=$(hg tags | grep -v ^tip | head -1 | awk '{ print $1 }') +out="/tmp/cq-$tag.tar.bz2" +echo "Archiving from tag $tag to file $out" +if [ -z "$tag" ]; then + echo "ERROR: No tag found!" + exit 1 +fi +hg archive -r"$tag" -S --exclude misc "$out"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/CQBase.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,133 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQBASE_H +#define CQBASE_H + +#include <vector> +#include <complex> + +/** + * Interface class for Constant-Q implementations, containing common + * type declarations and means to query configuration parameters. + */ +class CQBase +{ +public: + /// A single complex-valued sample. + typedef std::complex<double> Complex; + + /// A series of real-valued samples ordered in time. + typedef std::vector<double> RealSequence; + + /// A series of real-valued samples ordered by bin (frequency or similar). + typedef std::vector<double> RealColumn; + + /// A series of complex-valued samples ordered in time. + typedef std::vector<Complex> ComplexSequence; + + /// A series of complex-valued samples ordered by bin (frequency or similar). + typedef std::vector<Complex> ComplexColumn; + + /// A matrix of real-valued samples, indexed by time then bin number. + typedef std::vector<RealColumn> RealBlock; + + /// A matrix of complex-valued samples, indexed by time then bin number. + typedef std::vector<ComplexColumn> ComplexBlock; + + /** + * Return true if the Constant-Q implementation was successfully + * constructed, with a valid set of initialisation parameters. + */ + virtual bool isValid() const = 0; + + /** + * Return the sample rate used when constructing the specific + * Constant-Q implementation. + */ + virtual double getSampleRate() const = 0; + + /** + * Return the number of bins per octave specified when + * constructing the Constant-Q implementation. + */ + virtual int getBinsPerOctave() const = 0; + + /** + * Return the number of octaves spanned by the Constant-Q + * transform. + */ + virtual int getOctaves() const = 0; + + /** + * Return the total number of bins in each Constant-Q column + * (i.e. bins-per-octave times number of octaves). + */ + virtual int getTotalBins() const = 0; + + /** + * Return the spacing, in samples at the sample rate returned from + * getSampleRate(), between one column and the next. + */ + virtual int getColumnHop() const = 0; + + /** + * Return the latency of Constant-Q calculation, in samples at the + * sample rate returned from getSampleRate(). + */ + virtual int getLatency() const = 0; + + /** + * Return the maximum frequency of the Constant-Q output, i.e. the + * frequency of the highest bin in the output. This will normally + * be the same as the maximum frequency passed to the constructor + * of the specific Constant-Q implementation. + */ + virtual double getMaxFrequency() const = 0; + + /** + * Return the minimum frequency of the Constant-Q output, i.e. the + * frequency of the lowest bin in the output. This is derived from + * the maximum frequency and octave count, and is not necessarily + * the same as any minimum frequency requested when constructing + * the Constant-Q implementation. + */ + virtual double getMinFrequency() const = 0; + + /** + * Return the frequency of a given bin in the Constant-Q + * output. This actually maps a continuous "bin scale" value to + * frequency: the bin parameter does not have to be an integer. + */ + virtual double getBinFrequency(double bin) const = 0; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/CQInverse.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,117 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQINVERSE_H +#define CQINVERSE_H + +#include "CQBase.h" +#include "CQKernel.h" + +class Resampler; +class FFTReal; + +/** + * Calculate an inverse constant-Q transform. The input must be the + * same representation as returned as output of a \ref ConstantQ + * object with the same parameters. The output is a time-domain + * signal. + * + * Note that you cannot perform an inverse transform from the + * magnitude-only output of \ref CQSpectrogram; you need the complex + * valued data from \ref ConstantQ. + * + * Our implementation of the Constant-Q transform is not exactly + * invertible, and this produces only an approximation of the original + * signal (see publications for details). + */ +class CQInverse : public CQBase +{ +public: + /** + * Construct an inverse Constant-Q transform object using the + * given transform parameters. + */ + CQInverse(CQParameters params); + virtual ~CQInverse(); + + // CQBase methods, see CQBase.h for documentation + virtual bool isValid() const { return m_kernel && m_kernel->isValid(); } + virtual double getSampleRate() const { return m_sampleRate; } + virtual int getBinsPerOctave() const { return m_binsPerOctave; } + virtual int getOctaves() const { return m_octaves; } + virtual int getTotalBins() const { return m_octaves * m_binsPerOctave; } + virtual int getColumnHop() const { return m_p.fftHop / m_p.atomsPerFrame; } + virtual int getLatency() const { return m_outputLatency; } + virtual double getMaxFrequency() const { return m_p.maxFrequency; } + virtual double getMinFrequency() const; // actual min, not that passed to ctor + virtual double getBinFrequency(double bin) const; + + /** + * Given a series of constant-Q columns in the form produced by + * the \ref ConstantQ class, return a series of time-domain + * samples resulting from approximately inverting the constant-Q + * transform. + */ + RealSequence process(const ComplexBlock &); + + /** + * Return the remaining time-domain samples following the end of + * processing. + */ + RealSequence getRemainingOutput(); + +private: + const CQParameters m_inparams; + const double m_sampleRate; + const double m_maxFrequency; + const double m_minFrequency; + const int m_binsPerOctave; + + int m_octaves; + CQKernel *m_kernel; + CQKernel::Properties m_p; + + std::vector<Resampler *> m_upsamplers; + std::vector<RealSequence> m_buffers; + std::vector<RealSequence> m_olaBufs; // fixed-length, for overlap-add + + int m_outputLatency; + + FFTReal *m_fft; + + void initialise(); + void processOctave(int octave, const ComplexBlock &block); + void processOctaveColumn(int octave, const ComplexColumn &column); + void overlapAddAndResample(int octave, const RealSequence &); + RealSequence drawFromBuffers(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/CQKernel.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQ_KERNEL_H +#define CQ_KERNEL_H + +#include "CQParameters.h" + +#include <vector> +#include <complex> + +class FFT; + +class CQKernel +{ +public: + CQKernel(CQParameters params); + ~CQKernel(); + + bool isValid() const { return m_valid; } + + struct Properties { + double sampleRate; + double maxFrequency; + double minFrequency; + int binsPerOctave; + int fftSize; + int fftHop; + int atomsPerFrame; + int atomSpacing; + int firstCentre; + int lastCentre; + double Q; + }; + + Properties getProperties() const { return m_p; } + + std::vector<std::complex<double> > processForward + (const std::vector<std::complex<double> > &); + + std::vector<std::complex<double> > processInverse + (const std::vector<std::complex<double> > &); + +private: + const CQParameters m_inparams; + Properties m_p; + bool m_valid; + FFT *m_fft; + + struct KernelMatrix { + std::vector<int> origin; + std::vector<std::vector<std::complex<double> > > data; + }; + KernelMatrix m_kernel; + + std::vector<double> makeWindow(int len) const; + bool generateKernel(); + void finaliseKernel(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/CQParameters.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,131 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQ_PARAMETERS_H +#define CQ_PARAMETERS_H + +/** + * Common parameters for constructing Constant-Q implementation + * objects (both forward and inverse transforms). + */ +class CQParameters +{ +public: + enum WindowType { + SqrtBlackmanHarris, + SqrtBlackman, + SqrtHann, + BlackmanHarris, + Blackman, + Hann, + }; + + enum DecimatorType { + BetterDecimator, + FasterDecimator + }; + + /** + * Construct a set of parameters with the given input signal + * sample rate, frequency range, and number of bins per + * octave. The remaining parameters will take their usual + * defaults; if you want to change them, just assign the + * respective data members after construction. + */ + CQParameters(double _sampleRate, + double _minFrequency, + double _maxFrequency, + int _binsPerOctave) : + sampleRate(_sampleRate), + minFrequency(_minFrequency), + maxFrequency(_maxFrequency), + binsPerOctave(_binsPerOctave), + q(1.0), // Q scaling factor + atomHopFactor(0.25), // hop size of shortest temporal atom + threshold(0.0005), // sparsity threshold for resulting kernel + window(SqrtBlackmanHarris), // window shape + decimator(BetterDecimator) // decimator quality setting + { } + + /** + * Sampling rate of input signal. + */ + double sampleRate; + + /** + * Minimum frequency desired to include in Constant-Q output. The + * actual minimum will normally be calculated as a round number of + * octaves below the maximum frequency, and may differ from this. + */ + double minFrequency; + + /** + * Maximum frequency to include in Constant-Q output. + */ + double maxFrequency; + + /** + * Number of output frequency bins per octave. + */ + int binsPerOctave; + + /** + * Spectral atom bandwidth scaling factor. q == 1 is optimal for + * reconstruction, q < 1 increases redundancy (smearing) in the + * frequency domain but improves time resolution. + */ + double q; + + /** + * Hop size between temporal atoms, where 1 == no overlap and + * smaller values indicate overlapping atoms. + */ + double atomHopFactor; + + /** + * Sparsity threshold for Constant-Q kernel: values with magnitude + * smaller than this are truncated to zero. + */ + double threshold; + + /** + * Window shape to use for the Constant-Q kernel atoms. + */ + WindowType window; + + /** + * Quality setting for the sample rate decimator. + */ + DecimatorType decimator; +}; + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/CQSpectrogram.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,122 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQSPECTROGRAM_H +#define CQSPECTROGRAM_H + +#include "ConstantQ.h" + +/** + * Calculate a dense constant-Q magnitude spectrogram from time-domain + * input. The input of each \ref process call is a single frame of + * time-domain samples; the output is a series of fixed-height + * columns. See \ref process for details. + * + * If you need the full complex-valued constant-Q output, you must use + * the \ref ConstantQ class instead. + */ +class CQSpectrogram : public CQBase +{ +public: + enum Interpolation { + /// leave empty cells as zero + InterpolateZeros, + /// replace empty cells with a repeat of the previous column + InterpolateHold, + /// perform linear interpolation between consecutive time cells + InterpolateLinear, + }; + + /** + * Construct a Constant-Q magnitude spectrogram object using the + * given transform parameters. + */ + CQSpectrogram(CQParameters params, Interpolation interpolation); + virtual ~CQSpectrogram(); + + // CQBase methods, see CQBase.h for documentation + virtual bool isValid() const { return m_cq.isValid(); } + virtual double getSampleRate() const { return m_cq.getSampleRate(); } + virtual int getBinsPerOctave() const { return m_cq.getBinsPerOctave(); } + virtual int getOctaves() const { return m_cq.getOctaves(); } + virtual int getTotalBins() const { return m_cq.getTotalBins(); } + virtual int getColumnHop() const { return m_cq.getColumnHop(); } + virtual int getLatency() const { return m_cq.getLatency(); } + virtual double getMaxFrequency() const { return m_cq.getMaxFrequency(); } + virtual double getMinFrequency() const { return m_cq.getMinFrequency(); } + virtual double getBinFrequency(double bin) const { return m_cq.getBinFrequency(bin); } + + /** + * Given a series of time-domain samples, return a series of + * constant-Q magnitude columns. Any samples left over (that did + * not fit into a constant-Q processing block) are saved for the + * next call to process or getRemainingBlocks. + * + * The input is assumed to be a single frame of time-domain sample + * values, such that consecutive calls to \ref process receive + * contiguous frames from the source signal. Each frame may be of + * any length in samples. + * + * Each output column contains a series of constant-Q bin value + * magnitudes, ordered from highest to lowest frequency. + * + * The columns are all of the same height, but they might not all + * be populated, depending on the interpolation mode: in + * InterpolateZeros mode, the lower octaves (which are spaced more + * widely in the raw constant-Q than the highest octave) will + * contain zeros for the undefined values, but in the other + * interpolation modes every cell will be filled. + * + * To obtain raw, complex constant-Q bin values, use the ConstantQ + * class. + */ + RealBlock process(const RealSequence &); + + /** + * Return the remaining constant-Q magnitude columns following the + * end of processing. Any buffered input is padded so as to ensure + * that all input provided to process() will have been returned. + */ + RealBlock getRemainingOutput(); + +private: + ConstantQ m_cq; + Interpolation m_interpolation; + + RealBlock m_buffer; + RealBlock postProcess(const ComplexBlock &, bool insist); + RealBlock fetchHold(bool insist); + RealBlock fetchLinear(bool insist); + RealBlock linearInterpolated(const RealBlock &, int, int); + RealColumn m_prevColumn; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/Chromagram.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,137 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2015 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQCHROMAGRAM_H +#define CQCHROMAGRAM_H + +#include "CQBase.h" +#include "CQParameters.h" + +class CQSpectrogram; + +class Chromagram +{ +public: + struct Parameters { + Parameters(double sr) : + sampleRate(sr), + lowestOctave(0), + octaveCount(7), + binsPerOctave(36), + tuningFrequency(440.), + q(1.0), // Q scaling factor + atomHopFactor(0.25), // hop size of shortest temporal atom + threshold(0.0005), // sparsity threshold for resulting kernel + window(CQParameters::SqrtBlackmanHarris) // window shape + { } + + /** + * Sampling rate of input signal. + */ + double sampleRate; + + /** + * Octave number of lowest octave to include in the + * chromagram. Numbering is per ASA standard with -1 as the + * first octave in the MIDI range and middle-C being C4. The + * octave starts at C. + */ + int lowestOctave; + + /** + * Number of source constant-Q octaves to wrap around into the + * single-octave chroma output. + */ + int octaveCount; + + /** + * Number of constant-Q transform bins per octave and the + * number of bins in the chroma output. + */ + int binsPerOctave; + + /** + * Frequency of concert A, used when mapping the note-based + * octave extents into frequency extents for the constant-Q + * transform. + */ + double tuningFrequency; + + /** + * Spectral atom bandwidth scaling factor. + */ + double q; + + /** + * Hop size between temporal atoms, where 1 == no overlap and + * smaller values indicate overlapping atoms. + */ + double atomHopFactor; + + /** + * Sparsity threshold for Constant-Q kernel: values with + * magnitude smaller than this are truncated to zero. + */ + double threshold; + + /** + * Window shape to use for the Constant-Q kernel atoms. + */ + CQParameters::WindowType window; + }; + + Chromagram(Parameters params); + virtual ~Chromagram(); + + CQBase::RealBlock process(const CQBase::RealSequence &); + CQBase::RealBlock getRemainingOutput(); + + double getMinFrequency() const { return m_minFrequency; } + double getMaxFrequency() const { return m_maxFrequency; } + + std::string getBinName(int bin) const; + + bool isValid() const; + int getColumnHop() const; + int getLatency() const; + +private: + Parameters m_params; + CQSpectrogram *m_cq; + double m_minFrequency; + double m_maxFrequency; + CQBase::RealBlock convert(const CQBase::RealBlock &); +}; + +#endif + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cq/ConstantQ.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,130 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CONSTANTQ_H +#define CONSTANTQ_H + +#include "CQBase.h" +#include "CQParameters.h" +#include "CQKernel.h" + +class Resampler; +class FFTReal; + +/** + * Calculate a complex sparse constant-Q representation from + * time-domain input. The input of each \ref process call is a single + * frame of time-domain samples; the output is a series of columns of + * varying height. See \ref process for details. + * + * For a real (magnitude-only) interpolated dense representation, see + * CQSpectrogram. + */ +class ConstantQ : public CQBase +{ +public: + /** + * Construct a complex Constant-Q transform object using the given + * transform parameters. + */ + ConstantQ(CQParameters params); + virtual ~ConstantQ(); + + // CQBase methods, see CQBase.h for documentation + virtual bool isValid() const { return m_kernel && m_kernel->isValid(); } + virtual double getSampleRate() const { return m_sampleRate; } + virtual int getBinsPerOctave() const { return m_binsPerOctave; } + virtual int getOctaves() const { return m_octaves; } + virtual int getTotalBins() const { return m_octaves * m_binsPerOctave; } + virtual int getColumnHop() const { return m_p.fftHop / m_p.atomsPerFrame; } + virtual int getLatency() const { return m_outputLatency; } + virtual double getMaxFrequency() const { return m_p.maxFrequency; } + virtual double getMinFrequency() const; + virtual double getBinFrequency(double bin) const; // bin may be nonintegral + + /** + * Given a series of time-domain samples, return a series of + * constant-Q columns. Any samples left over (that did not fit + * into a constant-Q processing block) are saved for the next call + * to process or getRemainingBlocks. + * + * The input is assumed to be a single frame of time-domain sample + * values, such that consecutive calls to \ref process receive + * contiguous frames from the source signal. Each frame may be of + * any length in samples. + * + * Each output column contains a series of constant-Q bin values + * ordered from highest to lowest frequency. + * + * Output columns are of varying height: each will contain at + * least getBinsPerOctave() values, because the highest-frequency + * octave is always present, but a second octave (if requested) + * will appear only in alternate columns, a third octave only in + * every fourth column, and so on. + * + * If you need a format in which all columns are of equal height + * and every bin contains a value, use \ref CQSpectrogram instead + * of ConstantQ. + */ + ComplexBlock process(const RealSequence &); + + /** + * Return the remaining constant-Q columns following the end of + * processing. Any buffered input is padded so as to ensure that + * all input provided to process() will have been returned. + */ + ComplexBlock getRemainingOutput(); + +private: + const CQParameters m_inparams; + const double m_sampleRate; + const double m_maxFrequency; + const double m_minFrequency; + const int m_binsPerOctave; + + int m_octaves; + CQKernel *m_kernel; + CQKernel::Properties m_p; + int m_bigBlockSize; + + std::vector<Resampler *> m_decimators; + std::vector<RealSequence> m_buffers; + + int m_outputLatency; + + FFTReal *m_fft; + + void initialise(); + ComplexBlock processOctaveBlock(int octave); +}; + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cqvamp.cat Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,3 @@ +vamp:cqvamp:cqvamp::Visualisation +vamp:cqvamp:cqvampmidi::Visualisation +vamp:cqvamp:cqchromavamp::Visualisation
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/cqvamp.n3 Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,263 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix vamp: <http://purl.org/ontology/vamp/> . +@prefix plugbase: <http://vamp-plugins.org/rdf/plugins/cqvamp#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix dc: <http://purl.org/dc/elements/1.1/> . +@prefix af: <http://purl.org/ontology/af/> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix cc: <http://web.resource.org/cc/> . +@prefix : <#> . + +<> a vamp:PluginDescription ; + foaf:maker <http://www.vamp-plugins.org/doap.rdf#template-generator> ; + foaf:primaryTopic <http://vamp-plugins.org/rdf/plugins/cqvamp> . + +:library_maker + foaf:name "Queen Mary, University of London" ; + foaf:logo <http://vamp-plugins.org/rdf/plugins/makers/qm.png> ; + foaf:page <http://c4dm.eecs.qmul.ac.uk/> . + +:cqvamp a vamp:PluginLibrary ; + vamp:identifier "cqvamp" ; + dc:title "Constant-Q" ; + dc:description "A plugin implementing the Constant-Q transform of a time-domain signal." ; + foaf:maker :library_maker ; + vamp:available_plugin plugbase:cqchromavamp ; + vamp:available_plugin plugbase:cqvamp ; + vamp:available_plugin plugbase:cqvampmidi ; + foaf:page <http://code.soundsoftware.ac.uk/projects/constant-q-cpp> ; + . + +plugbase:cqchromavamp a vamp:Plugin ; + dc:title "Chromagram" ; + vamp:name "Chromagram" ; + dc:description """Extract a Constant-Q spectrogram with constant ratio of centre frequency to resolution from the audio, then wrap it around into a single-octave chromagram.""" ; + foaf:maker :library_maker ; + dc:rights """Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2014 QMUL. BSD/MIT licence.""" ; +# cc:license <Place plugin license URI here and uncomment> ; + vamp:identifier "cqchromavamp" ; + vamp:vamp_API_version vamp:api_version_2 ; + owl:versionInfo "1" ; + vamp:input_domain vamp:TimeDomain ; + + vamp:parameter plugbase:cqchromavamp_param_lowestoct ; + vamp:parameter plugbase:cqchromavamp_param_octaves ; + vamp:parameter plugbase:cqchromavamp_param_tuning ; + vamp:parameter plugbase:cqchromavamp_param_bpo ; + + vamp:output plugbase:cqchromavamp_output_chromagram ; + . +plugbase:cqchromavamp_param_lowestoct a vamp:QuantizedParameter ; + vamp:identifier "lowestoct" ; + dc:title "Lowest Contributing Octave" ; + dc:format "" ; + vamp:min_value -1 ; + vamp:max_value 12 ; + vamp:unit "" ; + vamp:quantize_step 1 ; + vamp:default_value 0 ; + vamp:value_names (); + . +plugbase:cqchromavamp_param_octaves a vamp:QuantizedParameter ; + vamp:identifier "octaves" ; + dc:title "Contributing Octave Count" ; + dc:format "octaves" ; + vamp:min_value 1 ; + vamp:max_value 12 ; + vamp:unit "octaves" ; + vamp:quantize_step 1 ; + vamp:default_value 7 ; + vamp:value_names (); + . +plugbase:cqchromavamp_param_tuning a vamp:Parameter ; + vamp:identifier "tuning" ; + dc:title "Tuning Frequency" ; + dc:format "Hz" ; + vamp:min_value 360 ; + vamp:max_value 500 ; + vamp:unit "Hz" ; + vamp:default_value 440 ; + vamp:value_names (); + . +plugbase:cqchromavamp_param_bpo a vamp:QuantizedParameter ; + vamp:identifier "bpo" ; + dc:title "Bins per Octave" ; + dc:format "bins" ; + vamp:min_value 2 ; + vamp:max_value 480 ; + vamp:unit "bins" ; + vamp:quantize_step 1 ; + vamp:default_value 36 ; + vamp:value_names (); + . +plugbase:cqchromavamp_output_chromagram a vamp:DenseOutput ; + vamp:identifier "chromagram" ; + dc:title "Chromagram" ; + dc:description """Chromagram obtained from output of constant-Q transform, folding over each process block into a single-octave vector""" ; + vamp:fixed_bin_count "true" ; + vamp:unit "" ; + vamp:bin_count 36 ; +# vamp:computes_event_type <Place event type URI here and uncomment> ; +# vamp:computes_feature <Place feature attribute URI here and uncomment> ; +# vamp:computes_signal_type <Place signal type URI here and uncomment> ; + . +plugbase:cqvamp a vamp:Plugin ; + dc:title "Constant-Q Spectrogram (Hz range)" ; + vamp:name "Constant-Q Spectrogram (Hz range)" ; + dc:description """Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in Hz.""" ; + foaf:maker :library_maker ; + dc:rights """Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2014 QMUL. BSD/MIT licence.""" ; +# cc:license <Place plugin license URI here and uncomment> ; + vamp:identifier "cqvamp" ; + vamp:vamp_API_version vamp:api_version_2 ; + owl:versionInfo "1" ; + vamp:input_domain vamp:TimeDomain ; + + vamp:parameter plugbase:cqvamp_param_minfreq ; + vamp:parameter plugbase:cqvamp_param_maxfreq ; + vamp:parameter plugbase:cqvamp_param_bpo ; + vamp:parameter plugbase:cqvamp_param_interpolation ; + + vamp:output plugbase:cqvamp_output_constantq ; + . +plugbase:cqvamp_param_minfreq a vamp:Parameter ; + vamp:identifier "minfreq" ; + dc:title "Minimum Frequency" ; + dc:format "Hz" ; + vamp:min_value 1 ; + vamp:max_value 24000 ; + vamp:unit "Hz" ; + vamp:default_value 100 ; + vamp:value_names (); + . +plugbase:cqvamp_param_maxfreq a vamp:Parameter ; + vamp:identifier "maxfreq" ; + dc:title "Maximum Frequency" ; + dc:format "Hz" ; + vamp:min_value 1 ; + vamp:max_value 24000 ; + vamp:unit "Hz" ; + vamp:default_value 14080 ; + vamp:value_names (); + . +plugbase:cqvamp_param_bpo a vamp:QuantizedParameter ; + vamp:identifier "bpo" ; + dc:title "Bins per Octave" ; + dc:format "bins" ; + vamp:min_value 2 ; + vamp:max_value 480 ; + vamp:unit "bins" ; + vamp:quantize_step 1 ; + vamp:default_value 36 ; + vamp:value_names (); + . +plugbase:cqvamp_param_interpolation a vamp:QuantizedParameter ; + vamp:identifier "interpolation" ; + dc:title "Interpolation" ; + dc:format "" ; + vamp:min_value 0 ; + vamp:max_value 2 ; + vamp:unit "" ; + vamp:quantize_step 1 ; + vamp:default_value 2 ; + vamp:value_names ( "None, leave as zero" "None, repeat prior value" "Linear interpolation"); + . +plugbase:cqvamp_output_constantq a vamp:DenseOutput ; + vamp:identifier "constantq" ; + dc:title "Constant-Q Spectrogram" ; + dc:description """Output of constant-Q transform, as a single vector per process block""" ; + vamp:fixed_bin_count "true" ; + vamp:unit "" ; + vamp:bin_count 216 ; +# vamp:computes_event_type <Place event type URI here and uncomment> ; +# vamp:computes_feature <Place feature attribute URI here and uncomment> ; +# vamp:computes_signal_type <Place signal type URI here and uncomment> ; + . +plugbase:cqvampmidi a vamp:Plugin ; + dc:title "Constant-Q Spectrogram (MIDI pitch range)" ; + vamp:name "Constant-Q Spectrogram (MIDI pitch range)" ; + dc:description """Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in MIDI pitch units.""" ; + foaf:maker :library_maker ; + dc:rights """Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2014 QMUL. BSD/MIT licence.""" ; +# cc:license <Place plugin license URI here and uncomment> ; + vamp:identifier "cqvampmidi" ; + vamp:vamp_API_version vamp:api_version_2 ; + owl:versionInfo "1" ; + vamp:input_domain vamp:TimeDomain ; + + vamp:parameter plugbase:cqvampmidi_param_minpitch ; + vamp:parameter plugbase:cqvampmidi_param_maxpitch ; + vamp:parameter plugbase:cqvampmidi_param_tuning ; + vamp:parameter plugbase:cqvampmidi_param_bpo ; + vamp:parameter plugbase:cqvampmidi_param_interpolation ; + + vamp:output plugbase:cqvampmidi_output_constantq ; + . +plugbase:cqvampmidi_param_minpitch a vamp:QuantizedParameter ; + vamp:identifier "minpitch" ; + dc:title "Minimum Pitch" ; + dc:format "MIDI units" ; + vamp:min_value 0 ; + vamp:max_value 127 ; + vamp:unit "MIDI units" ; + vamp:quantize_step 1 ; + vamp:default_value 36 ; + vamp:value_names (); + . +plugbase:cqvampmidi_param_maxpitch a vamp:QuantizedParameter ; + vamp:identifier "maxpitch" ; + dc:title "Maximum Pitch" ; + dc:format "MIDI units" ; + vamp:min_value 0 ; + vamp:max_value 127 ; + vamp:unit "MIDI units" ; + vamp:quantize_step 1 ; + vamp:default_value 84 ; + vamp:value_names (); + . +plugbase:cqvampmidi_param_tuning a vamp:Parameter ; + vamp:identifier "tuning" ; + dc:title "Tuning Frequency" ; + dc:format "Hz" ; + vamp:min_value 360 ; + vamp:max_value 500 ; + vamp:unit "Hz" ; + vamp:default_value 440 ; + vamp:value_names (); + . +plugbase:cqvampmidi_param_bpo a vamp:QuantizedParameter ; + vamp:identifier "bpo" ; + dc:title "Bins per Octave" ; + dc:format "bins" ; + vamp:min_value 2 ; + vamp:max_value 480 ; + vamp:unit "bins" ; + vamp:quantize_step 1 ; + vamp:default_value 36 ; + vamp:value_names (); + . +plugbase:cqvampmidi_param_interpolation a vamp:QuantizedParameter ; + vamp:identifier "interpolation" ; + dc:title "Interpolation" ; + dc:format "" ; + vamp:min_value 0 ; + vamp:max_value 2 ; + vamp:unit "" ; + vamp:quantize_step 1 ; + vamp:default_value 2 ; + vamp:value_names ( "None, leave as zero" "None, repeat prior value" "Linear interpolation"); + . +plugbase:cqvampmidi_output_constantq a vamp:DenseOutput ; + vamp:identifier "constantq" ; + dc:title "Constant-Q Spectrogram" ; + dc:description """Output of constant-Q transform, as a single vector per process block""" ; + vamp:fixed_bin_count "true" ; + vamp:unit "" ; + vamp:bin_count 216 ; +# vamp:computes_event_type <Place event type URI here and uncomment> ; +# vamp:computes_feature <Place feature attribute URI here and uncomment> ; +# vamp:computes_signal_type <Place signal type URI here and uncomment> ; + . +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/CQInverse.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,419 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "CQInverse.h" + +#include "dsp/Resampler.h" +#include "dsp/MathUtilities.h" +#include "dsp/FFT.h" + +#include <algorithm> +#include <iostream> +#include <stdexcept> + +#include <cmath> + +using std::vector; +using std::cerr; +using std::endl; + +//#define DEBUG_CQ 1 + +CQInverse::CQInverse(CQParameters params) : + m_inparams(params), + m_sampleRate(params.sampleRate), + m_maxFrequency(params.maxFrequency), + m_minFrequency(params.minFrequency), + m_binsPerOctave(params.binsPerOctave), + m_fft(0) +{ + if (m_minFrequency <= 0.0 || m_maxFrequency <= 0.0) { + throw std::invalid_argument("Frequency extents must be positive"); + } + + initialise(); +} + +CQInverse::~CQInverse() +{ + delete m_fft; + for (int i = 0; i < (int)m_upsamplers.size(); ++i) { + delete m_upsamplers[i]; + } + delete m_kernel; +} + +double +CQInverse::getMinFrequency() const +{ + return m_p.minFrequency / pow(2.0, m_octaves - 1); +} + +double +CQInverse::getBinFrequency(double bin) const +{ + // our bins are returned in high->low order + bin = (getBinsPerOctave() * getOctaves()) - bin - 1; + return getMinFrequency() * pow(2, (bin / getBinsPerOctave())); +} + +void +CQInverse::initialise() +{ + m_octaves = int(ceil(log(m_maxFrequency / m_minFrequency) / log(2))); + + if (m_octaves < 1) { + m_kernel = 0; // incidentally causing isValid() to return false + return; + } + + m_kernel = new CQKernel(m_inparams); + m_p = m_kernel->getProperties(); + + // Use exact powers of two for resampling rates. They don't have + // to be related to our actual samplerate: the resampler only + // cares about the ratio, but it only accepts integer source and + // target rates, and if we start from the actual samplerate we + // risk getting non-integer rates for lower octaves + + int sourceRate = pow(2, m_octaves); + vector<int> latencies; + + // top octave, no resampling + latencies.push_back(0); + m_upsamplers.push_back(0); + + for (int i = 1; i < m_octaves; ++i) { + + int factor = pow(2, i); + + Resampler *r = new Resampler + (sourceRate / factor, sourceRate, 50, 0.05); + +#ifdef DEBUG_CQ + cerr << "inverse: octave " << i << ": resample from " << sourceRate/factor << " to " << sourceRate << endl; +#endif + + // See ConstantQ.cpp for discussion on latency -- output + // latency here is at target rate which, this way around, is + // what we want + + latencies.push_back(r->getLatency()); + m_upsamplers.push_back(r); + } + + // additionally we will have fftHop latency at individual octave + // rate (before upsampling) for the overlap-add in each octave + for (int i = 0; i < m_octaves; ++i) { + latencies[i] += m_p.fftHop * pow(2, i); + } + + // Now reverse the drop adjustment made in ConstantQ to align the + // atom centres across different octaves (but this time at output + // sample rate) + + int emptyHops = m_p.firstCentre / m_p.atomSpacing; + + vector<int> pushes; + for (int i = 0; i < m_octaves; ++i) { + int factor = pow(2, i); + int pushHops = emptyHops * pow(2, m_octaves - i - 1) - emptyHops; + int push = ((pushHops * m_p.fftHop) * factor) / m_p.atomsPerFrame; + pushes.push_back(push); + } + + int maxLatLessPush = 0; + for (int i = 0; i < m_octaves; ++i) { + int latLessPush = latencies[i] - pushes[i]; + if (latLessPush > maxLatLessPush) maxLatLessPush = latLessPush; + } + + int totalLatency = maxLatLessPush + 10; + if (totalLatency < 0) totalLatency = 0; + + m_outputLatency = totalLatency + m_p.firstCentre * pow(2, m_octaves-1); + +#ifdef DEBUG_CQ + cerr << "totalLatency = " << totalLatency << ", m_outputLatency = " << m_outputLatency << endl; +#endif + + for (int i = 0; i < m_octaves; ++i) { + + // Calculate the difference between the total latency applied + // across all octaves, and the existing latency due to the + // upsampler for this octave. + + int latencyPadding = totalLatency - latencies[i] + pushes[i]; + +#ifdef DEBUG_CQ + cerr << "octave " << i << ": push " << pushes[i] << ", resampler latency inc overlap space " << latencies[i] << ", latencyPadding = " << latencyPadding << " (/factor = " << latencyPadding / pow(2, i) << ")" << endl; +#endif + + m_buffers.push_back(RealSequence(latencyPadding, 0.0)); + } + + for (int i = 0; i < m_octaves; ++i) { + // Fixed-size buffer for IFFT overlap-add + m_olaBufs.push_back(RealSequence(m_p.fftSize, 0.0)); + } + + m_fft = new FFTReal(m_p.fftSize); +} + +CQInverse::RealSequence +CQInverse::process(const ComplexBlock &block) +{ + // The input data is of the form produced by ConstantQ::process -- + // an unknown number N of columns of varying height. We assert + // that N is a multiple of atomsPerFrame * 2^(octaves-1), as must + // be the case for data that came directly from our ConstantQ + // implementation. + + int widthProvided = block.size(); + + if (widthProvided == 0) { + return drawFromBuffers(); + } + + int blockWidth = m_p.atomsPerFrame * int(pow(2, m_octaves - 1)); + + if (widthProvided % blockWidth != 0) { + cerr << "ERROR: CQInverse::process: Input block size (" + << widthProvided + << ") must be a multiple of processing block width " + << "(atoms-per-frame * 2^(octaves-1) = " + << m_p.atomsPerFrame << " * 2^(" << m_octaves << "-1) = " + << blockWidth << ")" << endl; + throw std::invalid_argument + ("Input block size must be a multiple of processing block width"); + } + + // Procedure: + // + // 1. Slice the list of columns into a set of lists of columns, + // one per octave, each of width N / (2^octave-1) and height + // binsPerOctave, containing the values present in that octave + // + // 2. Group each octave list by atomsPerFrame columns at a time, + // and stack these so as to achieve a list, for each octave, of + // taller columns of height binsPerOctave * atomsPerFrame + // + // 3. For each taller column, take the product with the inverse CQ + // kernel (which is the conjugate of the forward kernel) and + // perform an inverse FFT + // + // 4. Overlap-add each octave's resynthesised blocks (unwindowed) + // + // 5. Resample each octave's overlap-add stream to the original + // rate + // + // 6. Sum the resampled streams and return + + for (int i = 0; i < m_octaves; ++i) { + + // Step 1 + + ComplexBlock oct; + + for (int j = 0; j < widthProvided; ++j) { + int h = block[j].size(); + if (h < m_binsPerOctave * (i+1)) { + continue; + } + ComplexColumn col(block[j].begin() + m_binsPerOctave * i, + block[j].begin() + m_binsPerOctave * (i+1)); + oct.push_back(col); + } + + // Steps 2, 3, 4, 5 + processOctave(i, oct); + } + + // Step 6 + return drawFromBuffers(); +} + +CQInverse::RealSequence +CQInverse::drawFromBuffers() +{ + // 6. Sum the resampled streams and return + + int available = 0; + + for (int i = 0; i < m_octaves; ++i) { + if (i == 0 || int(m_buffers[i].size()) < available) { + available = m_buffers[i].size(); + } + } + + RealSequence result(available, 0); + + if (available == 0) { + return result; + } + + for (int i = 0; i < m_octaves; ++i) { + for (int j = 0; j < available; ++j) { + result[j] += m_buffers[i][j]; + } + m_buffers[i] = RealSequence(m_buffers[i].begin() + available, + m_buffers[i].end()); + } + + return result; +} + +CQInverse::RealSequence +CQInverse::getRemainingOutput() +{ + for (int j = 0; j < m_octaves; ++j) { + int factor = pow(2, j); + int latency = (j > 0 ? m_upsamplers[j]->getLatency() : 0) / factor; + for (int i = 0; i < (latency + m_p.fftSize) / m_p.fftHop; ++i) { + overlapAddAndResample(j, RealSequence(m_olaBufs[j].size(), 0)); + } + } + + return drawFromBuffers(); +} + +void +CQInverse::processOctave(int octave, const ComplexBlock &columns) +{ + // 2. Group each octave list by atomsPerFrame columns at a time, + // and stack these so as to achieve a list, for each octave, of + // taller columns of height binsPerOctave * atomsPerFrame + + int ncols = columns.size(); + + if (ncols % m_p.atomsPerFrame != 0) { + cerr << "ERROR: CQInverse::process: Number of columns (" + << ncols + << ") in octave " << octave + << " must be a multiple of atoms-per-frame (" + << m_p.atomsPerFrame << ")" << endl; + throw std::invalid_argument + ("Columns in octave must be a multiple of atoms per frame"); + } + + for (int i = 0; i < ncols; i += m_p.atomsPerFrame) { + + ComplexColumn tallcol; + for (int b = 0; b < m_binsPerOctave; ++b) { + for (int a = 0; a < m_p.atomsPerFrame; ++a) { + tallcol.push_back(columns[i + a][m_binsPerOctave - b - 1]); + } + } + + processOctaveColumn(octave, tallcol); + } +} + +void +CQInverse::processOctaveColumn(int octave, const ComplexColumn &column) +{ + // 3. For each taller column, take the product with the inverse CQ + // kernel (which is the conjugate of the forward kernel) and + // perform an inverse FFT + + if ((int)column.size() != m_p.atomsPerFrame * m_binsPerOctave) { + cerr << "ERROR: CQInverse::processOctaveColumn: Height of column (" + << column.size() << ") in octave " << octave + << " must be atoms-per-frame * bins-per-octave (" + << m_p.atomsPerFrame << " * " << m_binsPerOctave << " = " + << m_p.atomsPerFrame * m_binsPerOctave << ")" << endl; + throw std::invalid_argument + ("Column height must match atoms-per-frame * bins-per-octave"); + } + + ComplexSequence transformed = m_kernel->processInverse(column); + + int halfLen = m_p.fftSize/2 + 1; + + RealSequence ri(halfLen, 0); + RealSequence ii(halfLen, 0); + + for (int i = 0; i < halfLen; ++i) { + ri[i] = transformed[i].real(); + ii[i] = transformed[i].imag(); + } + + RealSequence timeDomain(m_p.fftSize, 0); + + m_fft->inverse(ri.data(), ii.data(), timeDomain.data()); + + overlapAddAndResample(octave, timeDomain); +} + +void +CQInverse::overlapAddAndResample(int octave, const RealSequence &seq) +{ + // 4. Overlap-add each octave's resynthesised blocks (unwindowed) + // + // and + // + // 5. Resample each octave's overlap-add stream to the original + // rate + + if (seq.size() != m_olaBufs[octave].size()) { + cerr << "ERROR: CQInverse::overlapAdd: input sequence length (" + << seq.size() << ") is expected to match OLA buffer size (" + << m_olaBufs[octave].size() << ")" << endl; + throw std::invalid_argument + ("Input sequence length should match OLA buffer size"); + } + + RealSequence toResample(m_olaBufs[octave].begin(), + m_olaBufs[octave].begin() + m_p.fftHop); + + RealSequence resampled = + octave > 0 ? + m_upsamplers[octave]->process(toResample.data(), toResample.size()) : + toResample; + + m_buffers[octave].insert(m_buffers[octave].end(), + resampled.begin(), + resampled.end()); + + m_olaBufs[octave] = RealSequence(m_olaBufs[octave].begin() + m_p.fftHop, + m_olaBufs[octave].end()); + + RealSequence pad(m_p.fftHop, 0); + + m_olaBufs[octave].insert(m_olaBufs[octave].end(), + pad.begin(), + pad.end()); + + for (int i = 0; i < m_p.fftSize; ++i) { + m_olaBufs[octave][i] += seq[i]; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/CQKernel.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,395 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "CQKernel.h" + +#include "dsp/MathUtilities.h" +#include "dsp/FFT.h" +#include "dsp/Window.h" + +#include <cmath> +#include <cassert> +#include <vector> +#include <iostream> +#include <algorithm> + +#include <cmath> + +using std::vector; +using std::complex; +using std::cerr; +using std::endl; + +typedef std::complex<double> C; + +//#define DEBUG_CQ_KERNEL 1 + +CQKernel::CQKernel(CQParameters params) : + m_inparams(params), + m_valid(false), + m_fft(0) +{ + m_p.sampleRate = params.sampleRate; + m_p.maxFrequency = params.maxFrequency; + m_p.binsPerOctave = params.binsPerOctave; + m_valid = generateKernel(); +} + +CQKernel::~CQKernel() +{ + delete m_fft; +} + +vector<double> +CQKernel::makeWindow(int len) const +{ + // The MATLAB version uses a symmetric window, but our windows + // are periodic. A symmetric window of size N is a periodic + // one of size N-1 with the first element stuck on the end. + + WindowType wt(BlackmanHarrisWindow); + + switch (m_inparams.window) { + case CQParameters::SqrtBlackmanHarris: + case CQParameters::BlackmanHarris: + wt = BlackmanHarrisWindow; + break; + case CQParameters::SqrtBlackman: + case CQParameters::Blackman: + wt = BlackmanWindow; + break; + case CQParameters::SqrtHann: + case CQParameters::Hann: + wt = HanningWindow; + break; + } + + Window<double> w(wt, len-1); + vector<double> win = w.getWindowData(); + win.push_back(win[0]); + + switch (m_inparams.window) { + case CQParameters::SqrtBlackmanHarris: + case CQParameters::SqrtBlackman: + case CQParameters::SqrtHann: + for (int i = 0; i < (int)win.size(); ++i) { + win[i] = sqrt(win[i]) / len; + } + break; + case CQParameters::BlackmanHarris: + case CQParameters::Blackman: + case CQParameters::Hann: + for (int i = 0; i < (int)win.size(); ++i) { + win[i] = win[i] / len; + } + break; + } + + return win; +} + +bool +CQKernel::generateKernel() +{ + double q = m_inparams.q; + double atomHopFactor = m_inparams.atomHopFactor; + double thresh = m_inparams.threshold; + + double bpo = m_p.binsPerOctave; + + m_p.minFrequency = (m_p.maxFrequency / 2) * pow(2, 1.0/bpo); + m_p.Q = q / (pow(2, 1.0/bpo) - 1.0); + + double maxNK = int(m_p.Q * m_p.sampleRate / m_p.minFrequency + 0.5); + double minNK = int + (m_p.Q * m_p.sampleRate / + (m_p.minFrequency * pow(2, (bpo - 1.0) / bpo)) + 0.5); + + if (minNK == 0 || maxNK == 0) { + // most likely pathological parameters of some sort + cerr << "WARNING: CQKernel::generateKernel: minNK or maxNK is zero (minNK == " << minNK << ", maxNK == " << maxNK << "), not generating a kernel" << endl; + m_p.atomSpacing = 0; + m_p.firstCentre = 0; + m_p.fftSize = 0; + m_p.atomsPerFrame = 0; + m_p.lastCentre = 0; + m_p.fftHop = 0; + return false; + } + + m_p.atomSpacing = int(minNK * atomHopFactor + 0.5); + m_p.firstCentre = m_p.atomSpacing * ceil(ceil(maxNK / 2.0) / m_p.atomSpacing); + m_p.fftSize = MathUtilities::nextPowerOfTwo + (m_p.firstCentre + ceil(maxNK / 2.0)); + + m_p.atomsPerFrame = floor + (1.0 + (m_p.fftSize - ceil(maxNK / 2.0) - m_p.firstCentre) / m_p.atomSpacing); + +#ifdef DEBUG_CQ_KERNEL + cerr << "atomsPerFrame = " << m_p.atomsPerFrame << " (q = " << q << ", Q = " << m_p.Q << ", atomHopFactor = " << atomHopFactor << ", atomSpacing = " << m_p.atomSpacing << ", fftSize = " << m_p.fftSize << ", maxNK = " << maxNK << ", firstCentre = " << m_p.firstCentre << ")" << endl; +#endif + + m_p.lastCentre = m_p.firstCentre + (m_p.atomsPerFrame - 1) * m_p.atomSpacing; + + m_p.fftHop = (m_p.lastCentre + m_p.atomSpacing) - m_p.firstCentre; + +#ifdef DEBUG_CQ_KERNEL + cerr << "fftHop = " << m_p.fftHop << endl; +#endif + + m_fft = new FFT(m_p.fftSize); + + for (int k = 1; k <= m_p.binsPerOctave; ++k) { + + int nk = int(m_p.Q * m_p.sampleRate / + (m_p.minFrequency * pow(2, ((k-1.0) / bpo))) + 0.5); + + vector<double> win = makeWindow(nk); + + double fk = m_p.minFrequency * pow(2, ((k-1.0) / bpo)); + + vector<double> reals, imags; + + for (int i = 0; i < nk; ++i) { + double arg = (2.0 * M_PI * fk * i) / m_p.sampleRate; + reals.push_back(win[i] * cos(arg)); + imags.push_back(win[i] * sin(arg)); + } + + int atomOffset = m_p.firstCentre - int(ceil(nk/2.0)); + + for (int i = 0; i < m_p.atomsPerFrame; ++i) { + + int shift = atomOffset + (i * m_p.atomSpacing); + + vector<double> rin(m_p.fftSize, 0.0); + vector<double> iin(m_p.fftSize, 0.0); + + for (int j = 0; j < nk; ++j) { + rin[j + shift] = reals[j]; + iin[j + shift] = imags[j]; + } + + vector<double> rout(m_p.fftSize, 0.0); + vector<double> iout(m_p.fftSize, 0.0); + + m_fft->process(false, + rin.data(), iin.data(), + rout.data(), iout.data()); + + // Keep this dense for the moment (until after + // normalisation calculations) + + vector<C> row; + + for (int j = 0; j < m_p.fftSize; ++j) { + if (sqrt(rout[j] * rout[j] + iout[j] * iout[j]) < thresh) { + row.push_back(C(0, 0)); + } else { + row.push_back(C(rout[j] / m_p.fftSize, + iout[j] / m_p.fftSize)); + } + } + + m_kernel.origin.push_back(0); + m_kernel.data.push_back(row); + } + } + + assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame); + + // print density as diagnostic + + int nnz = 0; + for (int i = 0; i < (int)m_kernel.data.size(); ++i) { + for (int j = 0; j < (int)m_kernel.data[i].size(); ++j) { + if (m_kernel.data[i][j] != C(0, 0)) { + ++nnz; + } + } + } + +#ifdef DEBUG_CQ_KERNEL + cerr << "size = " << m_kernel.data.size() << "*" << m_kernel.data[0].size() << " (fft size = " << m_p.fftSize << ")" << endl; +#endif + + assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame); + assert((int)m_kernel.data[0].size() == m_p.fftSize); + +#ifdef DEBUG_CQ_KERNEL + cerr << "density = " << double(nnz) / double(m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize) << " (" << nnz << " of " << m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize << ")" << endl; +#endif + + finaliseKernel(); + return true; +} + +static bool ccomparator(C &c1, C &c2) +{ + return abs(c1) < abs(c2); +} + +static int maxidx(vector<C> &v) +{ + return std::max_element(v.begin(), v.end(), ccomparator) - v.begin(); +} + +void +CQKernel::finaliseKernel() +{ + // calculate weight for normalisation + + int wx1 = maxidx(m_kernel.data[0]); + int wx2 = maxidx(m_kernel.data[m_kernel.data.size()-1]); + + vector<vector<C> > subset(m_kernel.data.size()); + for (int j = wx1; j <= wx2; ++j) { + for (int i = 0; i < (int)m_kernel.data.size(); ++i) { + subset[i].push_back(m_kernel.data[i][j]); + } + } + + int nrows = subset.size(); + int ncols = subset[0].size(); + vector<vector<C> > square(ncols); // conjugate transpose of subset * subset + + for (int i = 0; i < nrows; ++i) { + assert((int)subset[i].size() == ncols); + } + + for (int j = 0; j < ncols; ++j) { + for (int i = 0; i < ncols; ++i) { + C v(0, 0); + for (int k = 0; k < nrows; ++k) { + v += subset[k][i] * conj(subset[k][j]); + } + square[i].push_back(v); + } + } + + vector<double> wK; + double q = m_inparams.q; + for (int i = int(1.0/q + 0.5); i < ncols - int(1.0/q + 0.5) - 2; ++i) { + wK.push_back(abs(square[i][i])); + } + + double weight = double(m_p.fftHop) / m_p.fftSize; + if (!wK.empty()) { + weight /= MathUtilities::mean(wK.data(), wK.size()); + } + weight = sqrt(weight); + +#ifdef DEBUG_CQ_KERNEL + cerr << "weight = " << weight << " (from " << wK.size() << " elements in wK, ncols = " << ncols << ", q = " << q << ")" << endl; +#endif + + // apply normalisation weight, make sparse, and store conjugate + // (we use the adjoint or conjugate transpose of the kernel matrix + // for the forward transform, the plain kernel for the inverse + // which we expect to be less common) + + KernelMatrix sk; + + for (int i = 0; i < (int)m_kernel.data.size(); ++i) { + + sk.origin.push_back(0); + sk.data.push_back(vector<C>()); + + int lastNZ = 0; + for (int j = (int)m_kernel.data[i].size()-1; j >= 0; --j) { + if (abs(m_kernel.data[i][j]) != 0.0) { + lastNZ = j; + break; + } + } + + bool haveNZ = false; + for (int j = 0; j <= lastNZ; ++j) { + if (haveNZ || abs(m_kernel.data[i][j]) != 0.0) { + if (!haveNZ) sk.origin[i] = j; + haveNZ = true; + sk.data[i].push_back(conj(m_kernel.data[i][j]) * weight); + } + } + } + + m_kernel = sk; +} + +vector<C> +CQKernel::processForward(const vector<C> &cv) +{ + // straightforward matrix multiply (taking into account m_kernel's + // slightly-sparse representation) + + if (m_kernel.data.empty()) return vector<C>(); + + int nrows = m_p.binsPerOctave * m_p.atomsPerFrame; + + vector<C> rv(nrows, C()); + + for (int i = 0; i < nrows; ++i) { + int len = m_kernel.data[i].size(); + for (int j = 0; j < len; ++j) { + rv[i] += cv[j + m_kernel.origin[i]] * m_kernel.data[i][j]; + } + } + + return rv; +} + +vector<C> +CQKernel::processInverse(const vector<C> &cv) +{ + // matrix multiply by conjugate transpose of m_kernel. This is + // actually the original kernel as calculated, we just stored the + // conjugate-transpose of the kernel because we expect to be doing + // more forward transforms than inverse ones. + + if (m_kernel.data.empty()) return vector<C>(); + + int ncols = m_p.binsPerOctave * m_p.atomsPerFrame; + int nrows = m_p.fftSize; + + vector<C> rv(nrows, C()); + + for (int j = 0; j < ncols; ++j) { + int i0 = m_kernel.origin[j]; + int i1 = i0 + m_kernel.data[j].size(); + for (int i = i0; i < i1; ++i) { + rv[i] += cv[j] * conj(m_kernel.data[j][i - i0]); + } + } + + return rv; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/CQSpectrogram.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,269 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "CQSpectrogram.h" + +#include <iostream> +#include <stdexcept> + +using std::cerr; +using std::endl; + +//#define DEBUG_CQSPECTROGRAM 1 + +CQSpectrogram::CQSpectrogram(CQParameters params, + Interpolation interpolation) : + m_cq(params), + m_interpolation(interpolation) +{ +} + +CQSpectrogram::~CQSpectrogram() +{ +} + +CQSpectrogram::RealBlock +CQSpectrogram::process(const RealSequence &td) +{ + return postProcess(m_cq.process(td), false); +} + +CQSpectrogram::RealBlock +CQSpectrogram::getRemainingOutput() +{ + return postProcess(m_cq.getRemainingOutput(), true); +} + +CQSpectrogram::RealBlock +CQSpectrogram::postProcess(const ComplexBlock &cq, bool insist) +{ + int width = cq.size(); + + // convert to magnitudes + RealBlock spec; + for (int i = 0; i < width; ++i) { + int height = cq[i].size(); + RealColumn col(height, 0); + for (int j = 0; j < height; ++j) { +#ifdef DEBUG_CQSPECTROGRAM + if (isnan(cq[i][j].real())) { + cerr << "WARNING: NaN in real at (" << i << "," << j << ")" << endl; + } + if (isnan(cq[i][j].imag())) { + cerr << "WARNING: NaN in imag at (" << i << "," << j << ")" << endl; + } +#endif + col[j] = abs(cq[i][j]); + } + spec.push_back(col); + } + + if (m_interpolation == InterpolateZeros) { + for (int i = 0; i < width; ++i) { + int sh = spec[i].size(); + int fh = getTotalBins(); + for (int j = sh; j < fh; ++j) { + spec[i].push_back(0); + } + } + return spec; + } + + for (int i = 0; i < width; ++i) { + m_buffer.push_back(spec[i]); + } + + if (m_interpolation == InterpolateHold) { + return fetchHold(insist); + } else { + return fetchLinear(insist); + } +} + +CQSpectrogram::RealBlock +CQSpectrogram::fetchHold(bool) +{ + RealBlock out; + + int width = m_buffer.size(); + int height = getTotalBins(); + + for (int i = 0; i < width; ++i) { + + RealColumn col = m_buffer[i]; + + int thisHeight = col.size(); + int prevHeight = m_prevColumn.size(); + + for (int j = thisHeight; j < height; ++j) { + if (j < prevHeight) { + col.push_back(m_prevColumn[j]); + } else { + col.push_back(0.0); + } + } + + m_prevColumn = col; + out.push_back(col); + } + + m_buffer.clear(); + + return out; +} + +CQSpectrogram::RealBlock +CQSpectrogram::fetchLinear(bool insist) +{ + RealBlock out; + + //!!! This is surprisingly messy. I must be missing something. + + // We can only return any data when we have at least one column + // that has the full height in the buffer, that is not the first + // column. + // + // If the first col has full height, and there is another one + // later that also does, then we can interpolate between those, up + // to but not including the second full height column. Then we + // drop and return the columns we interpolated, leaving the second + // full-height col as the first col in the buffer. And repeat as + // long as enough columns are available. + // + // If the first col does not have full height, then (so long as + // we're following the logic above) we must simply have not yet + // reached the first full-height column in the CQ output, and we + // can interpolate nothing. + + int width = m_buffer.size(); + int height = getTotalBins(); + + if (width == 0) return out; + + int firstFullHeight = -1; + int secondFullHeight = -1; + + for (int i = 0; i < width; ++i) { + if ((int)m_buffer[i].size() == height) { + if (firstFullHeight == -1) { + firstFullHeight = i; + } else if (secondFullHeight == -1) { + secondFullHeight = i; + break; + } + } + } + +// cerr << "fetchLinear: firstFullHeight = " << firstFullHeight << ", secondFullHeight = " << secondFullHeight << endl; + + if (firstFullHeight < 0) { + if (insist) { + return fetchHold(true); + } else { + return out; + } + } else if (firstFullHeight > 0) { + // can interpolate nothing, stash up to first full height & recurse + out = RealBlock(m_buffer.begin(), m_buffer.begin() + firstFullHeight); + m_buffer = RealBlock(m_buffer.begin() + firstFullHeight, m_buffer.end()); + RealBlock more = fetchLinear(insist); + out.insert(out.end(), more.begin(), more.end()); + return out; + } else if (secondFullHeight < 0) { + // firstFullHeight == 0, but there is no second full height -- + // wait for it unless insist flag is set + if (insist) { + return fetchHold(true); + } else { + return out; + } + } else { + // firstFullHeight == 0 and secondFullHeight also valid. Can interpolate + out = linearInterpolated(m_buffer, 0, secondFullHeight); + m_buffer = RealBlock(m_buffer.begin() + secondFullHeight, m_buffer.end()); + RealBlock more = fetchLinear(insist); + out.insert(out.end(), more.begin(), more.end()); + return out; + } +} + +CQSpectrogram::RealBlock +CQSpectrogram::linearInterpolated(const RealBlock &g, int x0, int x1) +{ + // g must be a grid with full-height columns at x0 and x1 + + if (x0 >= x1) { + throw std::logic_error("x0 >= x1"); + } + if (x1 >= (int)g.size()) { + throw std::logic_error("x1 >= g.size()"); + } + if (g[x0].size() != g[x1].size()) { + throw std::logic_error("x0 and x1 are not the same height"); + } + + int height = g[x0].size(); + int width = x1 - x0; + + RealBlock out(g.begin() + x0, g.begin() + x1); + + for (int y = 0; y < height; ++y) { + + int spacing = width; + for (int i = 1; i < width; ++i) { + int thisHeight = g[x0 + i].size(); + if (thisHeight > height) { + throw std::logic_error("First column not full-height"); + } + if (thisHeight > y) { + spacing = i; + break; + } + } + + if (spacing < 2) continue; + + for (int i = 0; i + spacing <= width; i += spacing) { + for (int j = 1; j < spacing; ++j) { + double proportion = double(j)/double(spacing); + double interpolated = + g[x0 + i][y] * (1.0 - proportion) + + g[x0 + i + spacing][y] * proportion; + out[i + j].push_back(interpolated); + } + } + } + + return out; +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/Chromagram.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,154 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2015 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "Chromagram.h" +#include "CQSpectrogram.h" +#include "Pitch.h" + +#include <cstdio> + +using namespace std; + +Chromagram::Chromagram(Parameters params) : + m_params(params), + m_cq(0) +{ + int highestOctave = m_params.lowestOctave + m_params.octaveCount - 1; + + int midiPitchLimit = (1 + highestOctave) * 12 + 12; // C just beyond top + double midiPitchLimitFreq = Pitch::getFrequencyForPitch + (midiPitchLimit, 0, m_params.tuningFrequency); + + // Max frequency is frequency of the MIDI pitch just beyond the + // top octave range (midiPitchLimit) minus one bin, then minus + // floor(bins per semitone / 2) + int bps = m_params.binsPerOctave / 12; + m_maxFrequency = midiPitchLimitFreq / + pow(2.0, (1.0 + floor(bps/2)) / m_params.binsPerOctave); + + // Min frequency is frequency of midiPitchLimit lowered by the + // appropriate number of octaveCount. + m_minFrequency = midiPitchLimitFreq / + pow(2.0, m_params.octaveCount + 1); + + CQParameters p + (params.sampleRate, m_minFrequency, m_maxFrequency, params.binsPerOctave); + + p.q = params.q; + p.atomHopFactor = params.atomHopFactor; + p.threshold = params.threshold; + p.window = params.window; + + m_cq = new CQSpectrogram(p, CQSpectrogram::InterpolateLinear); +} + +Chromagram::~Chromagram() +{ + delete m_cq; +} + +bool +Chromagram::isValid() const +{ + return m_cq->isValid(); +} + +int +Chromagram::getColumnHop() const +{ + return m_cq->getColumnHop(); +} + +int +Chromagram::getLatency() const +{ + return m_cq->getLatency(); +} + +string +Chromagram::getBinName(int bin) const +{ + static const char *names[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + + float freq = m_cq->getBinFrequency(m_params.binsPerOctave - bin - 1); + int note = Pitch::getPitchForFrequency(freq, 0, m_params.tuningFrequency); + float nearestFreq = + Pitch::getFrequencyForPitch(note, 0, m_params.tuningFrequency); + + char name[40]; + sprintf(name, "%d", bin); + if (fabs(freq - nearestFreq) < 0.01) { + return (name + std::string(" ") + names[note % 12]); + } else { + return (name); + } +} + +CQBase::RealBlock +Chromagram::process(const CQBase::RealSequence &data) +{ + return convert(m_cq->process(data)); +} + +CQBase::RealBlock +Chromagram::getRemainingOutput() +{ + return convert(m_cq->getRemainingOutput()); +} + +CQBase::RealBlock +Chromagram::convert(const CQBase::RealBlock &cqout) +{ + CQBase::RealBlock chroma; + + int width = cqout.size(); + + for (int i = 0; i < width; ++i) { + + CQBase::RealSequence column(m_params.binsPerOctave, 0.); + + // fold and invert to put low frequencies at the start + + int thisHeight = cqout[i].size(); + + for (int j = 0; j < thisHeight; ++j) { + column[m_params.binsPerOctave - (j % m_params.binsPerOctave) - 1] + += cqout[i][j]; + } + + chroma.push_back(column); + } + + return chroma; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ConstantQ.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,369 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "ConstantQ.h" + +#include "CQKernel.h" + +#include "dsp/Resampler.h" +#include "dsp/MathUtilities.h" +#include "dsp/FFT.h" + +#include <algorithm> +#include <iostream> +#include <stdexcept> + +#include <cmath> + +using std::vector; +using std::cerr; +using std::endl; + +//#define DEBUG_CQ 1 + +ConstantQ::ConstantQ(CQParameters params) : + m_inparams(params), + m_sampleRate(params.sampleRate), + m_maxFrequency(params.maxFrequency), + m_minFrequency(params.minFrequency), + m_binsPerOctave(params.binsPerOctave), + m_kernel(0), + m_fft(0) +{ + if (m_minFrequency <= 0.0 || m_maxFrequency <= 0.0) { + throw std::invalid_argument("Frequency extents must be positive"); + } + + initialise(); +} + +ConstantQ::~ConstantQ() +{ + delete m_fft; + for (int i = 0; i < (int)m_decimators.size(); ++i) { + delete m_decimators[i]; + } + delete m_kernel; +} + +double +ConstantQ::getMinFrequency() const +{ + return m_p.minFrequency / pow(2.0, m_octaves - 1); +} + +double +ConstantQ::getBinFrequency(double bin) const +{ + // our bins are returned in high->low order + bin = (getBinsPerOctave() * getOctaves()) - bin - 1; + return getMinFrequency() * pow(2, (bin / getBinsPerOctave())); +} + +void +ConstantQ::initialise() +{ + m_octaves = int(ceil(log(m_maxFrequency / m_minFrequency) / log(2))); + + if (m_octaves < 1) { + m_kernel = 0; // incidentally causing isValid() to return false + return; + } + + m_kernel = new CQKernel(m_inparams); + m_p = m_kernel->getProperties(); + + if (!m_kernel->isValid()) { + return; + } + + // Use exact powers of two for resampling rates. They don't have + // to be related to our actual samplerate: the resampler only + // cares about the ratio, but it only accepts integer source and + // target rates, and if we start from the actual samplerate we + // risk getting non-integer rates for lower octaves + + int sourceRate = pow(2, m_octaves); + vector<int> latencies; + + // top octave, no resampling + latencies.push_back(0); + m_decimators.push_back(0); + + for (int i = 1; i < m_octaves; ++i) { + + int factor = pow(2, i); + + Resampler *r; + + if (m_inparams.decimator == CQParameters::BetterDecimator) { + r = new Resampler + (sourceRate, sourceRate / factor, 50, 0.05); + } else { + r = new Resampler + (sourceRate, sourceRate / factor, 25, 0.3); + } + +#ifdef DEBUG_CQ + cerr << "forward: octave " << i << ": resample from " << sourceRate << " to " << sourceRate / factor << endl; +#endif + + // We need to adapt the latencies so as to get the first input + // sample to be aligned, in time, at the decimator output + // across all octaves. + // + // Our decimator uses a linear phase filter, but being causal + // it is not zero phase: it has a latency that depends on the + // decimation factor. Those latencies have been calculated + // per-octave and are available to us in the latencies + // array. Left to its own devices, the first input sample will + // appear at output sample 0 in the highest octave (where no + // decimation is needed), sample number latencies[1] in the + // next octave down, latencies[2] in the next one, etc. We get + // to apply some artificial per-octave latency after the + // decimator in the processing chain, in order to compensate + // for the differing latencies associated with different + // decimation factors. How much should we insert? + // + // The outputs of the decimators are at different rates (in + // terms of the relation between clock time and samples) and + // we want them aligned in terms of time. So, for example, a + // latency of 10 samples with a decimation factor of 2 is + // equivalent to a latency of 20 with no decimation -- they + // both result in the first output sample happening at the + // same equivalent time in milliseconds. + // + // So here we record the latency added by the decimator, in + // terms of the sample rate of the undecimated signal. Then we + // use that to compensate in a moment, when we've discovered + // what the longest latency across all octaves is. + + latencies.push_back(r->getLatency() * factor); + m_decimators.push_back(r); + } + + m_bigBlockSize = m_p.fftSize * pow(2, m_octaves - 1); + + // Now add in the extra padding and compensate for hops that must + // be dropped in order to align the atom centres across + // octaves. Again this is a bit trickier because we are doing it + // at input rather than output and so must work in per-octave + // sample rates rather than output blocks + + int emptyHops = m_p.firstCentre / m_p.atomSpacing; + + vector<int> drops; + for (int i = 0; i < m_octaves; ++i) { + int factor = pow(2, i); + int dropHops = emptyHops * pow(2, m_octaves - i - 1) - emptyHops; + int drop = ((dropHops * m_p.fftHop) * factor) / m_p.atomsPerFrame; + drops.push_back(drop); + } + + int maxLatPlusDrop = 0; + for (int i = 0; i < m_octaves; ++i) { + int latPlusDrop = latencies[i] + drops[i]; + if (latPlusDrop > maxLatPlusDrop) maxLatPlusDrop = latPlusDrop; + } + + int totalLatency = maxLatPlusDrop; + + int lat0 = totalLatency - latencies[0] - drops[0]; + totalLatency = ceil(double(lat0 / m_p.fftHop) * m_p.fftHop) + + latencies[0] + drops[0]; + + // We want (totalLatency - latencies[i]) to be a multiple of 2^i + // for each octave i, so that we do not end up with fractional + // octave latencies below. In theory this is hard, in practice if + // we ensure it for the last octave we should be OK. + double finalOctLat = latencies[m_octaves-1]; + double finalOctFact = pow(2, m_octaves-1); + totalLatency = + int(finalOctLat + + finalOctFact * + ceil((totalLatency - finalOctLat) / finalOctFact) + .5); + +#ifdef DEBUG_CQ + cerr << "total latency = " << totalLatency << endl; +#endif + + // Padding as in the reference (will be introduced with the + // latency compensation in the loop below) + m_outputLatency = totalLatency + m_bigBlockSize + - m_p.firstCentre * pow(2, m_octaves-1); + +#ifdef DEBUG_CQ + cerr << "m_bigBlockSize = " << m_bigBlockSize << ", firstCentre = " + << m_p.firstCentre << ", m_octaves = " << m_octaves + << ", so m_outputLatency = " << m_outputLatency << endl; +#endif + + for (int i = 0; i < m_octaves; ++i) { + + double factor = pow(2, i); + + // Calculate the difference between the total latency applied + // across all octaves, and the existing latency due to the + // decimator for this octave, and then convert it back into + // the sample rate appropriate for the output latency of this + // decimator -- including one additional big block of padding + // (as in the reference). + + double octaveLatency = + double(totalLatency - latencies[i] - drops[i] + + m_bigBlockSize) / factor; + +#ifdef DEBUG_CQ + cerr << "octave " << i << ": resampler latency = " << latencies[i] + << ", drop " << drops[i] << " (/factor = " << drops[i]/factor + << "), octaveLatency = " << octaveLatency << " -> " + << int(round(octaveLatency)) << " (diff * factor = " + << (octaveLatency - round(octaveLatency)) << " * " + << factor << " = " + << (octaveLatency - round(octaveLatency)) * factor << ")" << endl; + + cerr << "double(" << totalLatency << " - " + << latencies[i] << " - " << drops[i] << " + " + << m_bigBlockSize << ") / " << factor << " = " + << octaveLatency << endl; +#endif + + m_buffers.push_back + (RealSequence(int(octaveLatency + 0.5), 0.0)); + } + + m_fft = new FFTReal(m_p.fftSize); +} + +ConstantQ::ComplexBlock +ConstantQ::process(const RealSequence &td) +{ + m_buffers[0].insert(m_buffers[0].end(), td.begin(), td.end()); + + for (int i = 1; i < m_octaves; ++i) { + RealSequence dec = m_decimators[i]->process(td.data(), td.size()); + m_buffers[i].insert(m_buffers[i].end(), dec.begin(), dec.end()); + } + + ComplexBlock out; + + while (true) { + + // We could have quite different remaining sample counts in + // different octaves, because (apart from the predictable + // added counts for decimator output on each block) we also + // have variable additional latency per octave + bool enough = true; + for (int i = 0; i < m_octaves; ++i) { + int required = m_p.fftSize * pow(2, m_octaves - i - 1); + if ((int)m_buffers[i].size() < required) { + enough = false; + } + } + if (!enough) break; + + int base = out.size(); + int totalColumns = pow(2, m_octaves - 1) * m_p.atomsPerFrame; + for (int i = 0; i < totalColumns; ++i) { + out.push_back(ComplexColumn()); + } + + for (int octave = 0; octave < m_octaves; ++octave) { + + int blocksThisOctave = pow(2, (m_octaves - octave - 1)); + + for (int b = 0; b < blocksThisOctave; ++b) { + ComplexBlock block = processOctaveBlock(octave); + + for (int j = 0; j < m_p.atomsPerFrame; ++j) { + + int target = base + + (b * (totalColumns / blocksThisOctave) + + (j * ((totalColumns / blocksThisOctave) / + m_p.atomsPerFrame))); + + while (int(out[target].size()) < + m_p.binsPerOctave * (octave + 1)) { + out[target].push_back(Complex()); + } + + for (int i = 0; i < m_p.binsPerOctave; ++i) { + out[target][m_p.binsPerOctave * octave + i] = + block[j][m_p.binsPerOctave - i - 1]; + } + } + } + } + } + + return out; +} + +ConstantQ::ComplexBlock +ConstantQ::getRemainingOutput() +{ + // Same as padding added at start, though rounded up + int pad = ceil(double(m_outputLatency) / m_bigBlockSize) * m_bigBlockSize; + RealSequence zeros(pad, 0.0); + return process(zeros); +} + +ConstantQ::ComplexBlock +ConstantQ::processOctaveBlock(int octave) +{ + RealSequence ro(m_p.fftSize, 0.0); + RealSequence io(m_p.fftSize, 0.0); + + m_fft->forward(m_buffers[octave].data(), ro.data(), io.data()); + + m_buffers[octave] = RealSequence(m_buffers[octave].begin() + m_p.fftHop, + m_buffers[octave].end()); + + ComplexSequence cv(m_p.fftSize); + for (int i = 0; i < m_p.fftSize; ++i) { + cv[i] = Complex(ro[i], io[i]); + } + + ComplexSequence cqrowvec = m_kernel->processForward(cv); + + // Reform into a column matrix + ComplexBlock cqblock; + for (int j = 0; j < m_p.atomsPerFrame; ++j) { + cqblock.push_back(ComplexColumn()); + for (int i = 0; i < m_p.binsPerOctave; ++i) { + cqblock[j].push_back(cqrowvec[i * m_p.atomsPerFrame + j]); + } + } + + return cqblock; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/Pitch.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,63 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "Pitch.h" + +#include <math.h> + +float +Pitch::getFrequencyForPitch(int midiPitch, + float centsOffset, + float concertA) +{ + float p = float(midiPitch) + (centsOffset / 100); + return concertA * powf(2.0, (p - 69.0) / 12.0); +} + +int +Pitch::getPitchForFrequency(float frequency, + float *centsOffsetReturn, + float concertA) +{ + float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0; + + int midiPitch = int(p + 0.00001); + float centsOffset = (p - midiPitch) * 100.0; + + if (centsOffset >= 50.0) { + midiPitch = midiPitch + 1; + centsOffset = -(100.0 - centsOffset); + } + + if (centsOffsetReturn) *centsOffsetReturn = centsOffset; + return midiPitch; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/Pitch.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,52 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _PITCH_H_ +#define _PITCH_H_ + +/** + * Convert between musical pitch (i.e. MIDI pitch number) and + * fundamental frequency. + */ +class Pitch +{ +public: + static float getFrequencyForPitch(int midiPitch, + float centsOffset = 0, + float concertA = 440.0); + + static int getPitchForFrequency(float frequency, + float *centsOffsetReturn = 0, + float concertA = 440.0); +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/FFT.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,225 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "FFT.h" + +#include "MathUtilities.h" + +#include "kiss_fft.h" +#include "kiss_fftr.h" + +#include <cmath> + +#include <iostream> + +#include <stdexcept> + +class FFT::D +{ +public: + D(int n) : m_n(n) { + m_planf = kiss_fft_alloc(m_n, 0, NULL, NULL); + m_plani = kiss_fft_alloc(m_n, 1, NULL, NULL); + m_kin = new kiss_fft_cpx[m_n]; + m_kout = new kiss_fft_cpx[m_n]; + } + + ~D() { + kiss_fft_free(m_planf); + kiss_fft_free(m_plani); + delete[] m_kin; + delete[] m_kout; + } + + void process(bool inverse, + const double *ri, + const double *ii, + double *ro, + double *io) { + + for (int i = 0; i < m_n; ++i) { + m_kin[i].r = ri[i]; + m_kin[i].i = (ii ? ii[i] : 0.0); + } + + if (!inverse) { + + kiss_fft(m_planf, m_kin, m_kout); + + for (int i = 0; i < m_n; ++i) { + ro[i] = m_kout[i].r; + io[i] = m_kout[i].i; + } + + } else { + + kiss_fft(m_plani, m_kin, m_kout); + + double scale = 1.0 / m_n; + + for (int i = 0; i < m_n; ++i) { + ro[i] = m_kout[i].r * scale; + io[i] = m_kout[i].i * scale; + } + } + } + +private: + int m_n; + kiss_fft_cfg m_planf; + kiss_fft_cfg m_plani; + kiss_fft_cpx *m_kin; + kiss_fft_cpx *m_kout; +}; + +FFT::FFT(int n) : + m_d(new D(n)) +{ +} + +FFT::~FFT() +{ + delete m_d; +} + +void +FFT::process(bool inverse, + const double *p_lpRealIn, const double *p_lpImagIn, + double *p_lpRealOut, double *p_lpImagOut) +{ + m_d->process(inverse, + p_lpRealIn, p_lpImagIn, + p_lpRealOut, p_lpImagOut); +} + +class FFTReal::D +{ +public: + D(int n) : m_n(n) { + if (n % 2) { + throw std::invalid_argument + ("nsamples must be even in FFTReal constructor"); + } + m_planf = kiss_fftr_alloc(m_n, 0, NULL, NULL); + m_plani = kiss_fftr_alloc(m_n, 1, NULL, NULL); + m_c = new kiss_fft_cpx[m_n]; + } + + ~D() { + kiss_fftr_free(m_planf); + kiss_fftr_free(m_plani); + delete[] m_c; + } + + void forward(const double *ri, double *ro, double *io) { + + kiss_fftr(m_planf, ri, m_c); + + for (int i = 0; i <= m_n/2; ++i) { + ro[i] = m_c[i].r; + io[i] = m_c[i].i; + } + + for (int i = 0; i + 1 < m_n/2; ++i) { + ro[m_n - i - 1] = ro[i + 1]; + io[m_n - i - 1] = -io[i + 1]; + } + } + + void forwardMagnitude(const double *ri, double *mo) { + + double *io = new double[m_n]; + + forward(ri, mo, io); + + for (int i = 0; i < m_n; ++i) { + mo[i] = sqrt(mo[i] * mo[i] + io[i] * io[i]); + } + + delete[] io; + } + + void inverse(const double *ri, const double *ii, double *ro) { + + // kiss_fftr.h says + // "input freqdata has nfft/2+1 complex points" + + for (int i = 0; i < m_n/2 + 1; ++i) { + m_c[i].r = ri[i]; + m_c[i].i = ii[i]; + } + + kiss_fftri(m_plani, m_c, ro); + + double scale = 1.0 / m_n; + + for (int i = 0; i < m_n; ++i) { + ro[i] *= scale; + } + } + +private: + int m_n; + kiss_fftr_cfg m_planf; + kiss_fftr_cfg m_plani; + kiss_fft_cpx *m_c; +}; + +FFTReal::FFTReal(int n) : + m_d(new D(n)) +{ +} + +FFTReal::~FFTReal() +{ + delete m_d; +} + +void +FFTReal::forward(const double *ri, double *ro, double *io) +{ + m_d->forward(ri, ro, io); +} + +void +FFTReal::forwardMagnitude(const double *ri, double *mo) +{ + m_d->forwardMagnitude(ri, mo); +} + +void +FFTReal::inverse(const double *ri, const double *ii, double *ro) +{ + m_d->inverse(ri, ii, ro); +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/FFT.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,128 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef FFT_H +#define FFT_H + +class FFT +{ +public: + /** + * Construct an FFT object to carry out complex-to-complex + * transforms of size nsamples. nsamples does not have to be a + * power of two. + */ + FFT(int nsamples); + ~FFT(); + + /** + * Carry out a forward or inverse transform (depending on the + * value of inverse) of size nsamples, where nsamples is the value + * provided to the constructor above. + * + * realIn and (where present) imagIn should contain nsamples each, + * and realOut and imagOut should point to enough space to receive + * nsamples each. + * + * imagIn may be NULL if the signal is real, but the other + * pointers must be valid. + * + * The inverse transform is scaled by 1/nsamples. + */ + void process(bool inverse, + const double *realIn, const double *imagIn, + double *realOut, double *imagOut); + +private: + class D; + D *m_d; +}; + +class FFTReal +{ +public: + /** + * Construct an FFT object to carry out real-to-complex transforms + * of size nsamples. nsamples does not have to be a power of two, + * but it does have to be even. (Use the complex-complex FFT above + * if you need an odd FFT size. This constructor will throw + * std::invalid_argument if nsamples is odd.) + */ + FFTReal(int nsamples); + ~FFTReal(); + + /** + * Carry out a forward real-to-complex transform of size nsamples, + * where nsamples is the value provided to the constructor above. + * + * realIn, realOut, and imagOut must point to (enough space for) + * nsamples values. For consistency with the FFT class above, and + * compatibility with existing code, the conjugate half of the + * output is returned even though it is redundant. + */ + void forward(const double *realIn, + double *realOut, double *imagOut); + + /** + * Carry out a forward real-to-complex transform of size nsamples, + * where nsamples is the value provided to the constructor + * above. Return only the magnitudes of the complex output values. + * + * realIn and magOut must point to (enough space for) nsamples + * values. For consistency with the FFT class above, and + * compatibility with existing code, the conjugate half of the + * output is returned even though it is redundant. + */ + void forwardMagnitude(const double *realIn, double *magOut); + + /** + * Carry out an inverse real transform (i.e. complex-to-real) of + * size nsamples, where nsamples is the value provided to the + * constructor above. + * + * realIn and imagIn should point to at least nsamples/2+1 values; + * if more are provided, only the first nsamples/2+1 values of + * each will be used (the conjugate half will always be deduced + * from the first nsamples/2+1 rather than being read from the + * input data). realOut should point to enough space to receive + * nsamples values. + * + * The inverse transform is scaled by 1/nsamples. + */ + void inverse(const double *realIn, const double *imagIn, + double *realOut); + +private: + class D; + D *m_d; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/KaiserWindow.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,83 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "KaiserWindow.h" + +#include "MathUtilities.h" + +KaiserWindow::Parameters +KaiserWindow::parametersForTransitionWidth(double attenuation, + double transition) +{ + Parameters p; + p.length = 1 + (attenuation > 21.0 ? + ceil((attenuation - 7.95) / (2.285 * transition)) : + ceil(5.79 / transition)); + p.beta = (attenuation > 50.0 ? + 0.1102 * (attenuation - 8.7) : + attenuation > 21.0 ? + 0.5842 * pow(attenuation - 21.0, 0.4) + 0.07886 * (attenuation - 21.0) : + 0); + return p; +} + +static double besselTerm(double x, int i) +{ + if (i == 0) { + return 1; + } else { + double f = MathUtilities::factorial(i); + return pow(x/2, i*2) / (f*f); + } +} + +static double bessel0(double x) +{ + double b = 0.0; + for (int i = 0; i < 20; ++i) { + b += besselTerm(x, i); + } + return b; +} + +void +KaiserWindow::init() +{ + double denominator = bessel0(m_beta); + bool even = (m_length % 2 == 0); + for (int i = 0; i < (even ? m_length/2 : (m_length+1)/2); ++i) { + double k = double(2*i) / double(m_length-1) - 1.0; + m_window.push_back(bessel0(m_beta * sqrt(1.0 - k*k)) / denominator); + } + for (int i = 0; i < (even ? m_length/2 : (m_length-1)/2); ++i) { + m_window.push_back(m_window[int(m_length/2) - i - 1]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/KaiserWindow.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,125 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef KAISER_WINDOW_H +#define KAISER_WINDOW_H + +#include <vector> +#include <cmath> + +#include "pi.h" + +/** + * Kaiser window: A windower whose bandwidth and sidelobe height + * (signal-noise ratio) can be specified. These parameters are traded + * off against the window length. + */ +class KaiserWindow +{ +public: + struct Parameters { + int length; + double beta; + }; + + /** + * Construct a Kaiser windower with the given length and beta + * parameter. + */ + KaiserWindow(Parameters p) : m_length(p.length), m_beta(p.beta) { init(); } + + /** + * Construct a Kaiser windower with the given attenuation in dB + * and transition width in samples. + */ + static KaiserWindow byTransitionWidth(double attenuation, + double transition) { + return KaiserWindow + (parametersForTransitionWidth(attenuation, transition)); + } + + /** + * Construct a Kaiser windower with the given attenuation in dB + * and transition bandwidth in Hz for the given samplerate. + */ + static KaiserWindow byBandwidth(double attenuation, + double bandwidth, + double samplerate) { + return KaiserWindow + (parametersForBandwidth(attenuation, bandwidth, samplerate)); + } + + /** + * Obtain the parameters necessary for a Kaiser window of the + * given attenuation in dB and transition width in samples. + */ + static Parameters parametersForTransitionWidth(double attenuation, + double transition); + + /** + * Obtain the parameters necessary for a Kaiser window of the + * given attenuation in dB and transition bandwidth in Hz for the + * given samplerate. + */ + static Parameters parametersForBandwidth(double attenuation, + double bandwidth, + double samplerate) { + return parametersForTransitionWidth + (attenuation, (bandwidth * 2 * M_PI) / samplerate); + } + + int getLength() const { + return m_length; + } + + const double *getWindow() const { + return m_window.data(); + } + + void cut(double *src) const { + cut(src, src); + } + + void cut(const double *src, double *dst) const { + for (int i = 0; i < m_length; ++i) { + dst[i] = src[i] * m_window[i]; + } + } + +private: + int m_length; + double m_beta; + std::vector<double> m_window; + + void init(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/MathUtilities.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,419 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "MathUtilities.h" + +#include <iostream> +#include <algorithm> +#include <vector> +#include <cmath> + + +double MathUtilities::mod(double x, double y) +{ + double a = floor( x / y ); + + double b = x - ( y * a ); + return b; +} + +double MathUtilities::princarg(double ang) +{ + double ValOut; + + ValOut = mod( ang + M_PI, -2 * M_PI ) + M_PI; + + return ValOut; +} + +void MathUtilities::getAlphaNorm(const double *data, unsigned int len, unsigned int alpha, double* ANorm) +{ + unsigned int i; + double temp = 0.0; + double a=0.0; + + for( i = 0; i < len; i++) + { + temp = data[ i ]; + + a += ::pow( fabs(temp), double(alpha) ); + } + a /= ( double )len; + a = ::pow( a, ( 1.0 / (double) alpha ) ); + + *ANorm = a; +} + +double MathUtilities::getAlphaNorm( const std::vector <double> &data, unsigned int alpha ) +{ + unsigned int i; + unsigned int len = data.size(); + double temp = 0.0; + double a=0.0; + + for( i = 0; i < len; i++) + { + temp = data[ i ]; + + a += ::pow( fabs(temp), double(alpha) ); + } + a /= ( double )len; + a = ::pow( a, ( 1.0 / (double) alpha ) ); + + return a; +} + +double MathUtilities::round(double x) +{ + if (x < 0) { + return -floor(-x + 0.5); + } else { + return floor(x + 0.5); + } +} + +double MathUtilities::median(const double *src, unsigned int len) +{ + if (len == 0) return 0; + + std::vector<double> scratch; + for (int i = 0; i < (int)len; ++i) scratch.push_back(src[i]); + std::sort(scratch.begin(), scratch.end()); + + int middle = len/2; + if (len % 2 == 0) { + return (scratch[middle] + scratch[middle - 1]) / 2; + } else { + return scratch[middle]; + } +} + +double MathUtilities::sum(const double *src, unsigned int len) +{ + unsigned int i ; + double retVal =0.0; + + for( i = 0; i < len; i++) + { + retVal += src[ i ]; + } + + return retVal; +} + +double MathUtilities::mean(const double *src, unsigned int len) +{ + double retVal =0.0; + + if (len == 0) return 0; + + double s = sum( src, len ); + + retVal = s / (double)len; + + return retVal; +} + +double MathUtilities::mean(const std::vector<double> &src, + unsigned int start, + unsigned int count) +{ + double sum = 0.; + + if (count == 0) return 0; + + for (int i = 0; i < (int)count; ++i) + { + sum += src[start + i]; + } + + return sum / count; +} + +void MathUtilities::getFrameMinMax(const double *data, unsigned int len, double *min, double *max) +{ + unsigned int i; + double temp = 0.0; + + if (len == 0) { + *min = *max = 0; + return; + } + + *min = data[0]; + *max = data[0]; + + for( i = 0; i < len; i++) + { + temp = data[ i ]; + + if( temp < *min ) + { + *min = temp ; + } + if( temp > *max ) + { + *max = temp ; + } + + } +} + +int MathUtilities::getMax( double* pData, unsigned int Length, double* pMax ) +{ + unsigned int index = 0; + unsigned int i; + double temp = 0.0; + + double max = pData[0]; + + for( i = 0; i < Length; i++) + { + temp = pData[ i ]; + + if( temp > max ) + { + max = temp ; + index = i; + } + + } + + if (pMax) *pMax = max; + + + return index; +} + +int MathUtilities::getMax( const std::vector<double> & data, double* pMax ) +{ + unsigned int index = 0; + unsigned int i; + double temp = 0.0; + + double max = data[0]; + + for( i = 0; i < data.size(); i++) + { + temp = data[ i ]; + + if( temp > max ) + { + max = temp ; + index = i; + } + + } + + if (pMax) *pMax = max; + + + return index; +} + +void MathUtilities::circShift( double* pData, int length, int shift) +{ + shift = shift % length; + double temp; + int i,n; + + for( i = 0; i < shift; i++) + { + temp=*(pData + length - 1); + + for( n = length-2; n >= 0; n--) + { + *(pData+n+1)=*(pData+n); + } + + *pData = temp; + } +} + +int MathUtilities::compareInt (const void * a, const void * b) +{ + return ( *(int*)a - *(int*)b ); +} + +void MathUtilities::normalise(double *data, int length, NormaliseType type) +{ + switch (type) { + + case NormaliseNone: return; + + case NormaliseUnitSum: + { + double sum = 0.0; + for (int i = 0; i < length; ++i) { + sum += data[i]; + } + if (sum != 0.0) { + for (int i = 0; i < length; ++i) { + data[i] /= sum; + } + } + } + break; + + case NormaliseUnitMax: + { + double max = 0.0; + for (int i = 0; i < length; ++i) { + if (fabs(data[i]) > max) { + max = fabs(data[i]); + } + } + if (max != 0.0) { + for (int i = 0; i < length; ++i) { + data[i] /= max; + } + } + } + break; + + } +} + +void MathUtilities::normalise(std::vector<double> &data, NormaliseType type) +{ + switch (type) { + + case NormaliseNone: return; + + case NormaliseUnitSum: + { + double sum = 0.0; + for (int i = 0; i < (int)data.size(); ++i) sum += data[i]; + if (sum != 0.0) { + for (int i = 0; i < (int)data.size(); ++i) data[i] /= sum; + } + } + break; + + case NormaliseUnitMax: + { + double max = 0.0; + for (int i = 0; i < (int)data.size(); ++i) { + if (fabs(data[i]) > max) max = fabs(data[i]); + } + if (max != 0.0) { + for (int i = 0; i < (int)data.size(); ++i) data[i] /= max; + } + } + break; + + } +} + +void MathUtilities::adaptiveThreshold(std::vector<double> &data) +{ + int sz = int(data.size()); + if (sz == 0) return; + + std::vector<double> smoothed(sz); + + int p_pre = 8; + int p_post = 7; + + for (int i = 0; i < sz; ++i) { + + int first = std::max(0, i - p_pre); + int last = std::min(sz - 1, i + p_post); + + smoothed[i] = mean(data, first, last - first + 1); + } + + for (int i = 0; i < sz; i++) { + data[i] -= smoothed[i]; + if (data[i] < 0.0) data[i] = 0.0; + } +} + +bool +MathUtilities::isPowerOfTwo(int x) +{ + if (x < 1) return false; + if (x & (x-1)) return false; + return true; +} + +int +MathUtilities::nextPowerOfTwo(int x) +{ + if (isPowerOfTwo(x)) return x; + if (x < 1) return 1; + int n = 1; + while (x) { x >>= 1; n <<= 1; } + return n; +} + +int +MathUtilities::previousPowerOfTwo(int x) +{ + if (isPowerOfTwo(x)) return x; + if (x < 1) return 1; + int n = 1; + x >>= 1; + while (x) { x >>= 1; n <<= 1; } + return n; +} + +int +MathUtilities::nearestPowerOfTwo(int x) +{ + if (isPowerOfTwo(x)) return x; + int n0 = previousPowerOfTwo(x), n1 = nextPowerOfTwo(x); + if (x - n0 < n1 - x) return n0; + else return n1; +} + +double +MathUtilities::factorial(int x) +{ + if (x < 0) return 0; + double f = 1; + for (int i = 1; i <= x; ++i) { + f = f * i; + } + return f; +} + +int +MathUtilities::gcd(int a, int b) +{ + int c = a % b; + if (c == 0) { + return b; + } else { + return gcd(b, c); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/MathUtilities.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,154 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef MATHUTILITIES_H +#define MATHUTILITIES_H + +#include <vector> + +#include "nan-inf.h" +#include "pi.h" + +/** + * Static helper functions for simple mathematical calculations. + */ +class MathUtilities +{ +public: + /** + * Round x to the nearest integer. + */ + static double round( double x ); + + /** + * Return through min and max pointers the highest and lowest + * values in the given array of the given length. + */ + static void getFrameMinMax( const double* data, unsigned int len, double* min, double* max ); + + /** + * Return the mean of the given array of the given length. + */ + static double mean( const double* src, unsigned int len ); + + /** + * Return the mean of the subset of the given vector identified by + * start and count. + */ + static double mean( const std::vector<double> &data, + unsigned int start, unsigned int count ); + + /** + * Return the sum of the values in the given array of the given + * length. + */ + static double sum( const double* src, unsigned int len ); + + /** + * Return the median of the values in the given array of the given + * length. If the array is even in length, the returned value will + * be half-way between the two values adjacent to median. + */ + static double median( const double* src, unsigned int len ); + + /** + * The principle argument function. Map the phase angle ang into + * the range [-pi,pi). + */ + static double princarg( double ang ); + + /** + * Floating-point division modulus: return x % y. + */ + static double mod( double x, double y); + + static void getAlphaNorm(const double *data, unsigned int len, unsigned int alpha, double* ANorm); + static double getAlphaNorm(const std::vector <double> &data, unsigned int alpha ); + + static void circShift( double* data, int length, int shift); + + static int getMax( double* data, unsigned int length, double* max = 0 ); + static int getMax( const std::vector<double> &data, double* max = 0 ); + static int compareInt(const void * a, const void * b); + + enum NormaliseType { + NormaliseNone, + NormaliseUnitSum, + NormaliseUnitMax + }; + + static void normalise(double *data, int length, + NormaliseType n = NormaliseUnitMax); + + static void normalise(std::vector<double> &data, + NormaliseType n = NormaliseUnitMax); + + /** + * Threshold the input/output vector data against a moving-mean + * average filter. + */ + static void adaptiveThreshold(std::vector<double> &data); + + /** + * Return true if x is 2^n for some integer n >= 0. + */ + static bool isPowerOfTwo(int x); + + /** + * Return the next higher integer power of two from x, e.g. 1300 + * -> 2048, 2048 -> 2048. + */ + static int nextPowerOfTwo(int x); + + /** + * Return the next lower integer power of two from x, e.g. 1300 -> + * 1024, 2048 -> 2048. + */ + static int previousPowerOfTwo(int x); + + /** + * Return the nearest integer power of two to x, e.g. 1300 -> 1024, + * 12 -> 16 (not 8; if two are equidistant, the higher is returned). + */ + static int nearestPowerOfTwo(int x); + + /** + * Return x! + */ + static double factorial(int x); // returns double in case it is large + + /** + * Return the greatest common divisor of natural numbers a and b. + */ + static int gcd(int a, int b); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/Resampler.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,435 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "Resampler.h" + +#include "MathUtilities.h" +#include "KaiserWindow.h" +#include "SincWindow.h" + +#include <iostream> +#include <vector> +#include <map> +#include <cassert> +#include <algorithm> + +using std::vector; +using std::map; +using std::cerr; +using std::endl; + +//#define DEBUG_RESAMPLER 1 +//#define DEBUG_RESAMPLER_VERBOSE 1 + +Resampler::Resampler(int sourceRate, int targetRate) : + m_sourceRate(sourceRate), + m_targetRate(targetRate) +{ +#ifdef DEBUG_RESAMPLER + cerr << "Resampler::Resampler(" << sourceRate << "," << targetRate << ")" << endl; +#endif + initialise(100, 0.02); +} + +Resampler::Resampler(int sourceRate, int targetRate, + double snr, double bandwidth) : + m_sourceRate(sourceRate), + m_targetRate(targetRate) +{ + initialise(snr, bandwidth); +} + +Resampler::~Resampler() +{ + delete[] m_phaseData; +} + +void +Resampler::initialise(double snr, double bandwidth) +{ + int higher = std::max(m_sourceRate, m_targetRate); + int lower = std::min(m_sourceRate, m_targetRate); + + m_gcd = MathUtilities::gcd(lower, higher); + m_peakToPole = higher / m_gcd; + + if (m_targetRate < m_sourceRate) { + // antialiasing filter, should be slightly below nyquist + m_peakToPole = m_peakToPole / (1.0 - bandwidth/2.0); + } + + KaiserWindow::Parameters params = + KaiserWindow::parametersForBandwidth(snr, bandwidth, higher / m_gcd); + + params.length = + (params.length % 2 == 0 ? params.length + 1 : params.length); + + params.length = + (params.length > 200001 ? 200001 : params.length); + + m_filterLength = params.length; + + vector<double> filter; + + KaiserWindow kw(params); + SincWindow sw(m_filterLength, m_peakToPole * 2); + + filter = vector<double>(m_filterLength, 0.0); + for (int i = 0; i < m_filterLength; ++i) filter[i] = 1.0; + sw.cut(filter.data()); + kw.cut(filter.data()); + + int inputSpacing = m_targetRate / m_gcd; + int outputSpacing = m_sourceRate / m_gcd; + +#ifdef DEBUG_RESAMPLER + cerr << "resample " << m_sourceRate << " -> " << m_targetRate + << ": inputSpacing " << inputSpacing << ", outputSpacing " + << outputSpacing << ": filter length " << m_filterLength + << endl; +#endif + + // Now we have a filter of (odd) length flen in which the lower + // sample rate corresponds to every n'th point and the higher rate + // to every m'th where n and m are higher and lower rates divided + // by their gcd respectively. So if x coordinates are on the same + // scale as our filter resolution, then source sample i is at i * + // (targetRate / gcd) and target sample j is at j * (sourceRate / + // gcd). + + // To reconstruct a single target sample, we want a buffer (real + // or virtual) of flen values formed of source samples spaced at + // intervals of (targetRate / gcd), in our example case 3. This + // is initially formed with the first sample at the filter peak. + // + // 0 0 0 0 a 0 0 b 0 + // + // and of course we have our filter + // + // f1 f2 f3 f4 f5 f6 f7 f8 f9 + // + // We take the sum of products of non-zero values from this buffer + // with corresponding values in the filter + // + // a * f5 + b * f8 + // + // Then we drop (sourceRate / gcd) values, in our example case 4, + // from the start of the buffer and fill until it has flen values + // again + // + // a 0 0 b 0 0 c 0 0 + // + // repeat to reconstruct the next target sample + // + // a * f1 + b * f4 + c * f7 + // + // and so on. + // + // Above I said the buffer could be "real or virtual" -- ours is + // virtual. We don't actually store all the zero spacing values, + // except for padding at the start; normally we store only the + // values that actually came from the source stream, along with a + // phase value that tells us how many virtual zeroes there are at + // the start of the virtual buffer. So the two examples above are + // + // 0 a b [ with phase 1 ] + // a b c [ with phase 0 ] + // + // Having thus broken down the buffer so that only the elements we + // need to multiply are present, we can also unzip the filter into + // every-nth-element subsets at each phase, allowing us to do the + // filter multiplication as a simply vector multiply. That is, rather + // than store + // + // f1 f2 f3 f4 f5 f6 f7 f8 f9 + // + // we store separately + // + // f1 f4 f7 + // f2 f5 f8 + // f3 f6 f9 + // + // Each time we complete a multiply-and-sum, we need to work out + // how many (real) samples to drop from the start of our buffer, + // and how many to add at the end of it for the next multiply. We + // know we want to drop enough real samples to move along by one + // computed output sample, which is our outputSpacing number of + // virtual buffer samples. Depending on the relationship between + // input and output spacings, this may mean dropping several real + // samples, one real sample, or none at all (and simply moving to + // a different "phase"). + + m_phaseData = new Phase[inputSpacing]; + + for (int phase = 0; phase < inputSpacing; ++phase) { + + Phase p; + + p.nextPhase = phase - outputSpacing; + while (p.nextPhase < 0) p.nextPhase += inputSpacing; + p.nextPhase %= inputSpacing; + + p.drop = int(ceil(std::max(0.0, double(outputSpacing - phase)) + / inputSpacing)); + + int filtZipLength = int(ceil(double(m_filterLength - phase) + / inputSpacing)); + + for (int i = 0; i < filtZipLength; ++i) { + p.filter.push_back(filter[i * inputSpacing + phase]); + } + + m_phaseData[phase] = p; + } + +#ifdef DEBUG_RESAMPLER + int cp = 0; + int totDrop = 0; + for (int i = 0; i < inputSpacing; ++i) { + cerr << "phase = " << cp << ", drop = " << m_phaseData[cp].drop + << ", filter length = " << m_phaseData[cp].filter.size() + << ", next phase = " << m_phaseData[cp].nextPhase << endl; + totDrop += m_phaseData[cp].drop; + cp = m_phaseData[cp].nextPhase; + } + cerr << "total drop = " << totDrop << endl; +#endif + + // The May implementation of this uses a pull model -- we ask the + // resampler for a certain number of output samples, and it asks + // its source stream for as many as it needs to calculate + // those. This means (among other things) that the source stream + // can be asked for enough samples up-front to fill the buffer + // before the first output sample is generated. + // + // In this implementation we're using a push model in which a + // certain number of source samples is provided and we're asked + // for as many output samples as that makes available. But we + // can't return any samples from the beginning until half the + // filter length has been provided as input. This means we must + // either return a very variable number of samples (none at all + // until the filter fills, then half the filter length at once) or + // else have a lengthy declared latency on the output. We do the + // latter. (What do other implementations do?) + // + // We want to make sure the first "real" sample will eventually be + // aligned with the centre sample in the filter (it's tidier, and + // easier to do diagnostic calculations that way). So we need to + // pick the initial phase and buffer fill accordingly. + // + // Example: if the inputSpacing is 2, outputSpacing is 3, and + // filter length is 7, + // + // x x x x a b c ... input samples + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ... + // i j k l ... output samples + // [--------|--------] <- filter with centre mark + // + // Let h be the index of the centre mark, here 3 (generally + // int(filterLength/2) for odd-length filters). + // + // The smallest n such that h + n * outputSpacing > filterLength + // is 2 (that is, ceil((filterLength - h) / outputSpacing)), and + // (h + 2 * outputSpacing) % inputSpacing == 1, so the initial + // phase is 1. + // + // To achieve our n, we need to pre-fill the "virtual" buffer with + // 4 zero samples: the x's above. This is int((h + n * + // outputSpacing) / inputSpacing). It's the phase that makes this + // buffer get dealt with in such a way as to give us an effective + // index for sample a of 9 rather than 8 or 10 or whatever. + // + // This gives us output latency of 2 (== n), i.e. output samples i + // and j will appear before the one in which input sample a is at + // the centre of the filter. + + int h = int(m_filterLength / 2); + int n = ceil(double(m_filterLength - h) / outputSpacing); + + m_phase = (h + n * outputSpacing) % inputSpacing; + + int fill = (h + n * outputSpacing) / inputSpacing; + + m_latency = n; + + m_buffer = vector<double>(fill, 0); + m_bufferOrigin = 0; + +#ifdef DEBUG_RESAMPLER + cerr << "initial phase " << m_phase << " (as " << (m_filterLength/2) << " % " << inputSpacing << ")" + << ", latency " << m_latency << endl; +#endif +} + +double +Resampler::reconstructOne() +{ + Phase &pd = m_phaseData[m_phase]; + double v = 0.0; + int n = pd.filter.size(); + + if (n + m_bufferOrigin > (int)m_buffer.size()) { + cerr << "ERROR: n + m_bufferOrigin > m_buffer.size() [" << n << " + " + << m_bufferOrigin << " > " << m_buffer.size() << "]" << endl; + throw std::logic_error("n + m_bufferOrigin > m_buffer.size()"); + } + +#if defined(__MSVC__) +#define R__ __restrict +#elif defined(__GNUC__) +#define R__ __restrict__ +#else +#define R__ +#endif + + const double *const R__ buf(m_buffer.data() + m_bufferOrigin); + const double *const R__ filt(pd.filter.data()); + + for (int i = 0; i < n; ++i) { + // NB gcc can only vectorize this with -ffast-math + v += buf[i] * filt[i]; + } + + m_bufferOrigin += pd.drop; + m_phase = pd.nextPhase; + return v; +} + +int +Resampler::process(const double *src, double *dst, int n) +{ + m_buffer.insert(m_buffer.end(), src, src + n); + + int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate)); + int outidx = 0; + +#ifdef DEBUG_RESAMPLER + cerr << "process: buf siz " << m_buffer.size() << " filt siz for phase " << m_phase << " " << m_phaseData[m_phase].filter.size() << endl; +#endif + + double scaleFactor = (double(m_targetRate) / m_gcd) / m_peakToPole; + + while (outidx < maxout && + m_buffer.size() >= m_phaseData[m_phase].filter.size() + m_bufferOrigin) { + dst[outidx] = scaleFactor * reconstructOne(); + outidx++; + } + + if (m_bufferOrigin > (int)m_buffer.size()) { + cerr << "ERROR: m_bufferOrigin > m_buffer.size() [" + << m_bufferOrigin << " > " << m_buffer.size() << "]" << endl; + throw std::logic_error("m_bufferOrigin > m_buffer.size()"); + } + + m_buffer = vector<double>(m_buffer.begin() + m_bufferOrigin, m_buffer.end()); + m_bufferOrigin = 0; + + return outidx; +} + +vector<double> +Resampler::process(const double *src, int n) +{ + int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate)); + vector<double> out(maxout, 0.0); + int got = process(src, out.data(), n); + assert(got <= maxout); + if (got < maxout) out.resize(got); + return out; +} + +vector<double> +Resampler::resample(int sourceRate, int targetRate, const double *data, int n) +{ + Resampler r(sourceRate, targetRate); + + int latency = r.getLatency(); + + // latency is the output latency. We need to provide enough + // padding input samples at the end of input to guarantee at + // *least* the latency's worth of output samples. that is, + + int inputPad = int(ceil((double(latency) * sourceRate) / targetRate)); + + // that means we are providing this much input in total: + + int n1 = n + inputPad; + + // and obtaining this much output in total: + + int m1 = int(ceil((double(n1) * targetRate) / sourceRate)); + + // in order to return this much output to the user: + + int m = int(ceil((double(n) * targetRate) / sourceRate)); + +#ifdef DEBUG_RESAMPLER + cerr << "n = " << n << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", m = " << m << ", latency = " << latency << ", inputPad = " << inputPad << ", m1 = " << m1 << ", n1 = " << n1 << ", n1 - n = " << n1 - n << endl; +#endif + + vector<double> pad(n1 - n, 0.0); + vector<double> out(m1 + 1, 0.0); + + int gotData = r.process(data, out.data(), n); + int gotPad = r.process(pad.data(), out.data() + gotData, pad.size()); + int got = gotData + gotPad; + +#ifdef DEBUG_RESAMPLER + cerr << "resample: " << n << " in, " << pad.size() << " padding, " << got << " out (" << gotData << " data, " << gotPad << " padding, latency = " << latency << ")" << endl; +#endif +#ifdef DEBUG_RESAMPLER_VERBOSE + int printN = 50; + cerr << "first " << printN << " in:" << endl; + for (int i = 0; i < printN && i < n; ++i) { + if (i % 5 == 0) cerr << endl << i << "... "; + cerr << data[i] << " "; + } + cerr << endl; +#endif + + int toReturn = got - latency; + if (toReturn > m) toReturn = m; + + vector<double> sliced(out.begin() + latency, + out.begin() + latency + toReturn); + +#ifdef DEBUG_RESAMPLER_VERBOSE + cerr << "first " << printN << " out (after latency compensation), length " << sliced.size() << ":"; + for (int i = 0; i < printN && i < sliced.size(); ++i) { + if (i % 5 == 0) cerr << endl << i << "... "; + cerr << sliced[i] << " "; + } + cerr << endl; +#endif + + return sliced; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/Resampler.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,119 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef RESAMPLER_H +#define RESAMPLER_H + +#include <vector> + +/** + * Resampler resamples a stream from one integer sample rate to + * another (arbitrary) rate, using a kaiser-windowed sinc filter. The + * results and performance are pretty similar to libraries such as + * libsamplerate, though this implementation does not support + * time-varying ratios (the ratio is fixed on construction). + * + * See also Decimator, which is faster and rougher but supports only + * power-of-two downsampling factors. + */ +class Resampler +{ +public: + /** + * Construct a Resampler to resample from sourceRate to + * targetRate. + */ + Resampler(int sourceRate, int targetRate); + + /** + * Construct a Resampler to resample from sourceRate to + * targetRate, using the given filter parameters. + */ + Resampler(int sourceRate, int targetRate, + double snr, double bandwidth); + + virtual ~Resampler(); + + /** + * Read n input samples from src and write resampled data to + * dst. The return value is the number of samples written, which + * will be no more than ceil((n * targetRate) / sourceRate). The + * caller must ensure the dst buffer has enough space for the + * samples returned. + */ + int process(const double *src, double *dst, int n); + + /** + * Read n input samples from src and return resampled data by + * value. + */ + std::vector<double> process(const double *src, int n); + + /** + * Return the number of samples of latency at the output due by + * the filter. (That is, the output will be delayed by this number + * of samples relative to the input.) + */ + int getLatency() const { return m_latency; } + + /** + * Carry out a one-off resample of a single block of n + * samples. The output is latency-compensated. + */ + static std::vector<double> resample + (int sourceRate, int targetRate, const double *data, int n); + +private: + int m_sourceRate; + int m_targetRate; + int m_gcd; + int m_filterLength; + int m_bufferLength; + int m_latency; + double m_peakToPole; + + struct Phase { + int nextPhase; + std::vector<double> filter; + int drop; + }; + + Phase *m_phaseData; + int m_phase; + std::vector<double> m_buffer; + int m_bufferOrigin; + + void initialise(double, double); + double reconstructOne(); +}; + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/SincWindow.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,63 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "SincWindow.h" + +#include <cmath> + +void +SincWindow::init() +{ + if (m_length < 1) { + return; + } else if (m_length < 2) { + m_window.push_back(1); + return; + } else { + + int n0 = (m_length % 2 == 0 ? m_length/2 : (m_length - 1)/2); + int n1 = (m_length % 2 == 0 ? m_length/2 : (m_length + 1)/2); + double m = 2 * M_PI / m_p; + + for (int i = 0; i < n0; ++i) { + double x = ((m_length / 2) - i) * m; + m_window.push_back(sin(x) / x); + } + + m_window.push_back(1.0); + + for (int i = 1; i < n1; ++i) { + double x = i * m; + m_window.push_back(sin(x) / x); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/SincWindow.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,81 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef SINC_WINDOW_H +#define SINC_WINDOW_H + +#include <vector> + +#include "pi.h" + +/** + * A window containing values of the sinc function, i.e. sin(x)/x with + * sinc(0) == 1, with x == 0 at the centre. + */ +class SincWindow +{ +public: + /** + * Construct a windower of the given length, containing the values + * of sinc(x) with x=0 in the middle, i.e. at sample (length-1)/2 + * for odd or (length/2)+1 for even length, such that the distance + * from -pi to pi (the nearest zero crossings either side of the + * peak) is p samples. + */ + SincWindow(int length, double p) : m_length(length), m_p(p) { init(); } + + int getLength() const { + return m_length; + } + + const double *getWindow() const { + return m_window.data(); + } + + void cut(double *src) const { + cut(src, src); + } + + void cut(const double *src, double *dst) const { + for (int i = 0; i < m_length; ++i) { + dst[i] = src[i] * m_window[i]; + } + } + +private: + int m_length; + double m_p; + std::vector<double> m_window; + + void init(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/Window.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,174 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef WINDOW_H +#define WINDOW_H + +#include <cmath> +#include <iostream> +#include <map> +#include <vector> + +#include "pi.h" + +enum WindowType { + RectangularWindow, + BartlettWindow, + HammingWindow, + HanningWindow, + BlackmanWindow, + BlackmanHarrisWindow, + + FirstWindow = RectangularWindow, + LastWindow = BlackmanHarrisWindow +}; + +/** + * Various shaped windows for sample frame conditioning, including + * cosine windows (Hann etc) and triangular and rectangular windows. + */ +template <typename T> +class Window +{ +public: + /** + * Construct a windower of the given type and size. + * + * Note that the cosine windows are periodic by design, rather + * than symmetrical. (A window of size N is equivalent to a + * symmetrical window of size N+1 with the final element missing.) + */ + Window(WindowType type, int size) : m_type(type), m_size(size) { encache(); } + Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); } + Window &operator=(const Window &w) { + if (&w == this) return *this; + m_type = w.m_type; + m_size = w.m_size; + encache(); + return *this; + } + virtual ~Window() { delete[] m_cache; } + + void cut(T *src) const { cut(src, src); } + void cut(const T *src, T *dst) const { + for (int i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + } + + WindowType getType() const { return m_type; } + int getSize() const { return m_size; } + + std::vector<T> getWindowData() const { + std::vector<T> d; + for (int i = 0; i < m_size; ++i) { + d.push_back(m_cache[i]); + } + return d; + } + +protected: + WindowType m_type; + int m_size; + T *m_cache; + + void encache(); +}; + +template <typename T> +void Window<T>::encache() +{ + int n = m_size; + T *mult = new T[n]; + int i; + for (i = 0; i < n; ++i) mult[i] = 1.0; + + switch (m_type) { + + case RectangularWindow: + for (i = 0; i < n; ++i) { + mult[i] = mult[i] * 0.5; + } + break; + + case BartlettWindow: + if (n == 2) { + mult[0] = mult[1] = 0; // "matlab compatible" + } else if (n == 3) { + mult[0] = 0; + mult[1] = mult[2] = 2./3.; + } else if (n > 3) { + for (i = 0; i < n/2; ++i) { + mult[i] = mult[i] * (i / T(n/2)); + mult[i + n - n/2] = mult[i + n - n/2] * (1.0 - (i / T(n/2))); + } + } + break; + + case HammingWindow: + if (n > 1) { + for (i = 0; i < n; ++i) { + mult[i] = mult[i] * (0.54 - 0.46 * cos(2 * M_PI * i / n)); + } + } + break; + + case HanningWindow: + if (n > 1) { + for (i = 0; i < n; ++i) { + mult[i] = mult[i] * (0.50 - 0.50 * cos(2 * M_PI * i / n)); + } + } + break; + + case BlackmanWindow: + if (n > 1) { + for (i = 0; i < n; ++i) { + mult[i] = mult[i] * (0.42 - 0.50 * cos(2 * M_PI * i / n) + + 0.08 * cos(4 * M_PI * i / n)); + } + } + break; + + case BlackmanHarrisWindow: + if (n > 1) { + for (i = 0; i < n; ++i) { + mult[i] = mult[i] * (0.35875 + - 0.48829 * cos(2 * M_PI * i / n) + + 0.14128 * cos(4 * M_PI * i / n) + - 0.01168 * cos(6 * M_PI * i / n)); + } + } + break; + } + + m_cache = mult; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/nan-inf.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,13 @@ + +#ifndef NAN_INF_H +#define NAN_INF_H + +#define ISNAN(x) (sizeof(x) == sizeof(double) ? ISNANd(x) : ISNANf(x)) +static inline int ISNANf(float x) { return x != x; } +static inline int ISNANd(double x) { return x != x; } + +#define ISINF(x) (sizeof(x) == sizeof(double) ? ISINFd(x) : ISINFf(x)) +static inline int ISINFf(float x) { return !ISNANf(x) && ISNANf(x - x); } +static inline int ISINFd(double x) { return !ISNANd(x) && ISNANd(x - x); } + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/dsp/pi.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,11 @@ + +#ifndef PI_H +#define PI_H + +#include <cmath> + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/.hgignore Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,10 @@ +syntax:glob +test/bm_* +test/st_* +test/tkfc_* +test/tr_* +tools/fastconv_* +tools/fastconvr_* +tools/fft_* +*.swp +*~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/.hgtags Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,21 @@ +32061530d4353ced38386a4578153954f46e5f8e v04_prehg +52a9ca23c9d9600794388912103e0f7d5f4c92ee v1_2_2_prehg +68e0375761a5789d72c4baa2f8265cec26fb546e v110_prehg +69a218df7458f0269a84b07f64678392edbff941 v111_prehg +702f90b11f060d45b23d563764a3b1bd8f851b7d about2do_real_multi_d_prehg +9c3041fb0677c071d7ca2797e22a63fe681d311b b4simd_prehg +9e532c64d324a0844f1efa7c32908fc2bc0cf350 v1_2_6_prehg +a9797b79bf2c61f7c8259ec417a0189caa48b17a v1_2_3_prehg +b1b2739c82378d6e04977440b2c31b7d1b34c266 aftersimd_prehg +b8c210e0bccdeb66930423a528b11b3660491af8 v120_prehg +c16172181d6aef63712a3f7bc31ef555ce7178bd help_prehg +c16172181d6aef63712a3f7bc31ef555ce7178bd v1_2_3a_prehg +c83c1ec6a5f21c40edf3bb9cbfd8ba1f862f1e0f half_bottle_o_wine_prehg +dec01bc9c4c5f807e2e04aef3449031bc5c6b69e v1_2_5_prehg +e6a840a1383ffc83c8ba17ed4b0bf5d6aefc9a34 v127_prehg +ea3a56794d9326d5505b5bf02f98a70217a1b86d v101_prehg +edd6236ecb25904b385856ffae608548ce3df4e5 v011_prehg +f19b7bcef3f322d277d5b51b2b132657cf08c1a7 v1_2_8_prehg +f73f8d58c37d52379e9d7f13d81f5391226209c1 v1_2_1_prehg +9e0bf8478cc337da0de18b7413d51c714aa0db46 v129 +b354a59534b0a77c43c67deb1eb1bc39eb99b487 v130
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/CHANGELOG Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,123 @@ +1.3.0 2012-07-18 + removed non-standard malloc.h from kiss_fft.h + + moved -lm to end of link line + + checked various return values + + converted python Numeric code to NumPy + + fixed test of int32_t on 64 bit OS + + added padding in a couple of places to allow SIMD alignment of structs + +1.2.9 2010-05-27 + threadsafe ( including OpenMP ) + + first edition of kissfft.hh the C++ template fft engine + +1.2.8 + Changed memory.h to string.h -- apparently more standard + + Added openmp extensions. This can have fairly linear speedups for larger FFT sizes. + +1.2.7 + Shrank the real-fft memory footprint. Thanks to Galen Seitz. + +1.2.6 (Nov 14, 2006) The "thanks to GenArts" release. + Added multi-dimensional real-optimized FFT, see tools/kiss_fftndr + Thanks go to GenArts, Inc. for sponsoring the development. + +1.2.5 (June 27, 2006) The "release for no good reason" release. + Changed some harmless code to make some compilers' warnings go away. + Added some more digits to pi -- why not. + Added kiss_fft_next_fast_size() function to help people decide how much to pad. + Changed multidimensional test from 8 dimensions to only 3 to avoid testing + problems with fixed point (sorry Buckaroo Banzai). + +1.2.4 (Oct 27, 2005) The "oops, inverse fixed point real fft was borked" release. + Fixed scaling bug for inverse fixed point real fft -- also fixed test code that should've been failing. + Thanks to Jean-Marc Valin for bug report. + + Use sys/types.h for more portable types than short,int,long => int16_t,int32_t,int64_t + If your system does not have these, you may need to define them -- but at least it breaks in a + loud and easily fixable way -- unlike silently using the wrong size type. + + Hopefully tools/psdpng.c is fixed -- thanks to Steve Kellog for pointing out the weirdness. + +1.2.3 (June 25, 2005) The "you want to use WHAT as a sample" release. + Added ability to use 32 bit fixed point samples -- requires a 64 bit intermediate result, a la 'long long' + + Added ability to do 4 FFTs in parallel by using SSE SIMD instructions. This is accomplished by + using the __m128 (vector of 4 floats) as kiss_fft_scalar. Define USE_SIMD to use this. + + I know, I know ... this is drifting a bit from the "kiss" principle, but the speed advantages + make it worth it for some. Also recent gcc makes it SOO easy to use vectors of 4 floats like a POD type. + +1.2.2 (May 6, 2005) The Matthew release + Replaced fixed point division with multiply&shift. Thanks to Jean-Marc Valin for + discussions regarding. Considerable speedup for fixed-point. + + Corrected overflow protection in real fft routines when using fixed point. + Finder's Credit goes to Robert Oschler of robodance for pointing me at the bug. + This also led to the CHECK_OVERFLOW_OP macro. + +1.2.1 (April 4, 2004) + compiles cleanly with just about every -W warning flag under the sun + + reorganized kiss_fft_state so it could be read-only/const. This may be useful for embedded systems + that are willing to predeclare twiddle factors, factorization. + + Fixed C_MUL,S_MUL on 16-bit platforms. + + tmpbuf will only be allocated if input & output buffers are same + scratchbuf will only be allocated for ffts that are not multiples of 2,3,5 + + NOTE: The tmpbuf,scratchbuf changes may require synchronization code for multi-threaded apps. + + +1.2 (Feb 23, 2004) + interface change -- cfg object is forward declaration of struct instead of void* + This maintains type saftey and lets the compiler warn/error about stupid mistakes. + (prompted by suggestion from Erik de Castro Lopo) + + small speed improvements + + added psdpng.c -- sample utility that will create png spectrum "waterfalls" from an input file + ( not terribly useful yet) + +1.1.1 (Feb 1, 2004 ) + minor bug fix -- only affects odd rank, in-place, multi-dimensional FFTs + +1.1 : (Jan 30,2004) + split sample_code/ into test/ and tools/ + + Removed 2-D fft and added N-D fft (arbitrary) + + modified fftutil.c to allow multi-d FFTs + + Modified core fft routine to allow an input stride via kiss_fft_stride() + (eased support of multi-D ffts) + + Added fast convolution filtering (FIR filtering using overlap-scrap method, with tail scrap) + + Add kfc.[ch]: the KISS FFT Cache. It takes care of allocs for you ( suggested by Oscar Lesta ). + +1.0.1 (Dec 15, 2003) + fixed bug that occurred when nfft==1. Thanks to Steven Johnson. + +1.0 : (Dec 14, 2003) + changed kiss_fft function from using a single buffer, to two buffers. + If the same buffer pointer is supplied for both in and out, kiss will + manage the buffer copies. + + added kiss_fft2d and kiss_fftr as separate source files (declarations in kiss_fft.h ) + +0.4 :(Nov 4,2003) optimized for radix 2,3,4,5 + +0.3 :(Oct 28, 2003) woops, version 2 didn't actually factor out any radices other than 2. + Thanks to Steven Johnson for finding this one. + +0.2 :(Oct 27, 2003) added mixed radix, only radix 2,4 optimized versions + +0.1 :(May 19 2003) initial release, radix 2 only
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/COPYING Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,11 @@ +Copyright (c) 2003-2010 Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/Makefile Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,33 @@ +KFVER=130 + +doc: + @echo "Start by reading the README file. If you want to build and test lots of stuff, do a 'make testall'" + @echo "but be aware that 'make testall' has dependencies that the basic kissfft software does not." + @echo "It is generally unneeded to run these tests yourself, unless you plan on changing the inner workings" + @echo "of kissfft and would like to make use of its regression tests." + +testall: + # The simd and int32_t types may or may not work on your machine + make -C test DATATYPE=simd CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=int32_t CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=int16_t CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=float CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=double CFLAGADD="$(CFLAGADD)" test + echo "all tests passed" + +tarball: clean + hg archive -r v$(KFVER) -t tgz kiss_fft$(KFVER).tar.gz + hg archive -r v$(KFVER) -t zip kiss_fft$(KFVER).zip + +clean: + cd test && make clean + cd tools && make clean + rm -f kiss_fft*.tar.gz *~ *.pyc kiss_fft*.zip + +asm: kiss_fft.s + +kiss_fft.s: kiss_fft.c kiss_fft.h _kiss_fft_guts.h + [ -e kiss_fft.s ] && mv kiss_fft.s kiss_fft.s~ || true + gcc -S kiss_fft.c -O3 -mtune=native -ffast-math -fomit-frame-pointer -unroll-loops -dA -fverbose-asm + gcc -o kiss_fft_short.s -S kiss_fft.c -O3 -mtune=native -ffast-math -fomit-frame-pointer -dA -fverbose-asm -DFIXED_POINT + [ -e kiss_fft.s~ ] && diff kiss_fft.s~ kiss_fft.s || true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/README Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,134 @@ +KISS FFT - A mixed-radix Fast Fourier Transform based up on the principle, +"Keep It Simple, Stupid." + + There are many great fft libraries already around. Kiss FFT is not trying +to be better than any of them. It only attempts to be a reasonably efficient, +moderately useful FFT that can use fixed or floating data types and can be +incorporated into someone's C program in a few minutes with trivial licensing. + +USAGE: + + The basic usage for 1-d complex FFT is: + + #include "kiss_fft.h" + + kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); + + while ... + + ... // put kth sample in cx_in[k].r and cx_in[k].i + + kiss_fft( cfg , cx_in , cx_out ); + + ... // transformed. DC is in cx_out[0].r and cx_out[0].i + + free(cfg); + + Note: frequency-domain data is stored from dc up to 2pi. + so cx_out[0] is the dc bin of the FFT + and cx_out[nfft/2] is the Nyquist bin (if exists) + + Declarations are in "kiss_fft.h", along with a brief description of the +functions you'll need to use. + +Code definitions for 1d complex FFTs are in kiss_fft.c. + +You can do other cool stuff with the extras you'll find in tools/ + + * multi-dimensional FFTs + * real-optimized FFTs (returns the positive half-spectrum: (nfft/2+1) complex frequency bins) + * fast convolution FIR filtering (not available for fixed point) + * spectrum image creation + +The core fft and most tools/ code can be compiled to use float, double, + Q15 short or Q31 samples. The default is float. + + +BACKGROUND: + + I started coding this because I couldn't find a fixed point FFT that didn't +use assembly code. I started with floating point numbers so I could get the +theory straight before working on fixed point issues. In the end, I had a +little bit of code that could be recompiled easily to do ffts with short, float +or double (other types should be easy too). + + Once I got my FFT working, I was curious about the speed compared to +a well respected and highly optimized fft library. I don't want to criticize +this great library, so let's call it FFT_BRANDX. +During this process, I learned: + + 1. FFT_BRANDX has more than 100K lines of code. The core of kiss_fft is about 500 lines (cpx 1-d). + 2. It took me an embarrassingly long time to get FFT_BRANDX working. + 3. A simple program using FFT_BRANDX is 522KB. A similar program using kiss_fft is 18KB (without optimizing for size). + 4. FFT_BRANDX is roughly twice as fast as KISS FFT in default mode. + + It is wonderful that free, highly optimized libraries like FFT_BRANDX exist. +But such libraries carry a huge burden of complexity necessary to extract every +last bit of performance. + + Sometimes simpler is better, even if it's not better. + +FREQUENTLY ASKED QUESTIONS: + Q: Can I use kissfft in a project with a ___ license? + A: Yes. See LICENSE below. + + Q: Why don't I get the output I expect? + A: The two most common causes of this are + 1) scaling : is there a constant multiplier between what you got and what you want? + 2) mixed build environment -- all code must be compiled with same preprocessor + definitions for FIXED_POINT and kiss_fft_scalar + + Q: Will you write/debug my code for me? + A: Probably not unless you pay me. I am happy to answer pointed and topical questions, but + I may refer you to a book, a forum, or some other resource. + + +PERFORMANCE: + (on Athlon XP 2100+, with gcc 2.96, float data type) + + Kiss performed 10000 1024-pt cpx ffts in .63 s of cpu time. + For comparison, it took md5sum twice as long to process the same amount of data. + + Transforming 5 minutes of CD quality audio takes less than a second (nfft=1024). + +DO NOT: + ... use Kiss if you need the Fastest Fourier Transform in the World + ... ask me to add features that will bloat the code + +UNDER THE HOOD: + + Kiss FFT uses a time decimation, mixed-radix, out-of-place FFT. If you give it an input buffer + and output buffer that are the same, a temporary buffer will be created to hold the data. + + No static data is used. The core routines of kiss_fft are thread-safe (but not all of the tools directory). + + No scaling is done for the floating point version (for speed). + Scaling is done both ways for the fixed-point version (for overflow prevention). + + Optimized butterflies are used for factors 2,3,4, and 5. + + The real (i.e. not complex) optimization code only works for even length ffts. It does two half-length + FFTs in parallel (packed into real&imag), and then combines them via twiddling. The result is + nfft/2+1 complex frequency bins from DC to Nyquist. If you don't know what this means, search the web. + + The fast convolution filtering uses the overlap-scrap method, slightly + modified to put the scrap at the tail. + +LICENSE: + Revised BSD License, see COPYING for verbiage. + Basically, "free to use&change, give credit where due, no guarantees" + Note this license is compatible with GPL at one end of the spectrum and closed, commercial software at + the other end. See http://www.fsf.org/licensing/licenses + + A commercial license is available which removes the requirement for attribution. Contact me for details. + + +TODO: + *) Add real optimization for odd length FFTs + *) Document/revisit the input/output fft scaling + *) Make doc describing the overlap (tail) scrap fast convolution filtering in kiss_fastfir.c + *) Test all the ./tools/ code with fixed point (kiss_fastfir.c doesn't work, maybe others) + +AUTHOR: + Mark Borgerding + Mark@Borgerding.net
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/README.simd Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,78 @@ +If you are reading this, it means you think you may be interested in using the SIMD extensions in kissfft +to do 4 *separate* FFTs at once. + +Beware! Beyond here there be dragons! + +This API is not easy to use, is not well documented, and breaks the KISS principle. + + +Still reading? Okay, you may get rewarded for your patience with a considerable speedup +(2-3x) on intel x86 machines with SSE if you are willing to jump through some hoops. + +The basic idea is to use the packed 4 float __m128 data type as a scalar element. +This means that the format is pretty convoluted. It performs 4 FFTs per fft call on signals A,B,C,D. + +For complex data, the data is interlaced as follows: +rA0,rB0,rC0,rD0, iA0,iB0,iC0,iD0, rA1,rB1,rC1,rD1, iA1,iB1,iC1,iD1 ... +where "rA0" is the real part of the zeroth sample for signal A + +Real-only data is laid out: +rA0,rB0,rC0,rD0, rA1,rB1,rC1,rD1, ... + +Compile with gcc flags something like +-O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -msse + +Be aware of SIMD alignment. This is the most likely cause of segfaults. +The code within kissfft uses scratch variables on the stack. +With SIMD, these must have addresses on 16 byte boundaries. +Search on "SIMD alignment" for more info. + + + +Robin at Divide Concept was kind enough to share his code for formatting to/from the SIMD kissfft. +I have not run it -- use it at your own risk. It appears to do 4xN and Nx4 transpositions +(out of place). + +void SSETools::pack128(float* target, float* source, unsigned long size128) +{ + __m128* pDest = (__m128*)target; + __m128* pDestEnd = pDest+size128; + float* source0=source; + float* source1=source0+size128; + float* source2=source1+size128; + float* source3=source2+size128; + + while(pDest<pDestEnd) + { + *pDest=_mm_set_ps(*source3,*source2,*source1,*source0); + source0++; + source1++; + source2++; + source3++; + pDest++; + } +} + +void SSETools::unpack128(float* target, float* source, unsigned long size128) +{ + + float* pSrc = source; + float* pSrcEnd = pSrc+size128*4; + float* target0=target; + float* target1=target0+size128; + float* target2=target1+size128; + float* target3=target2+size128; + + while(pSrc<pSrcEnd) + { + *target0=pSrc[0]; + *target1=pSrc[1]; + *target2=pSrc[2]; + *target3=pSrc[3]; + target0++; + target1++; + target2++; + target3++; + pSrc+=4; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/TIPS Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,39 @@ +Speed: + * If you want to use multiple cores, then compile with -openmp or -fopenmp (see your compiler docs). + Realize that larger FFTs will reap more benefit than smaller FFTs. This generally uses more CPU time, but + less wall time. + + * experiment with compiler flags + Special thanks to Oscar Lesta. He suggested some compiler flags + for gcc that make a big difference. They shave 10-15% off + execution time on some systems. Try some combination of: + -march=pentiumpro + -ffast-math + -fomit-frame-pointer + + * If the input data has no imaginary component, use the kiss_fftr code under tools/. + Real ffts are roughly twice as fast as complex. + + * If you can rearrange your code to do 4 FFTs in parallel and you are on a recent Intel or AMD machine, + then you might want to experiment with the USE_SIMD code. See README.simd + + +Reducing code size: + * remove some of the butterflies. There are currently butterflies optimized for radices + 2,3,4,5. It is worth mentioning that you can still use FFT sizes that contain + other factors, they just won't be quite as fast. You can decide for yourself + whether to keep radix 2 or 4. If you do some work in this area, let me + know what you find. + + * For platforms where ROM/code space is more plentiful than RAM, + consider creating a hardcoded kiss_fft_state. In other words, decide which + FFT size(s) you want and make a structure with the correct factors and twiddles. + + * Frank van der Hulst offered numerous suggestions for smaller code size and correct operation + on embedded targets. "I'm happy to help anyone who is trying to implement KISSFFT on a micro" + + Some of these were rolled into the mainline code base: + - using long casts to promote intermediate results of short*short multiplication + - delaying allocation of buffers that are sometimes unused. + In some cases, it may be desirable to limit capability in order to better suit the target: + - predefining the twiddle tables for the desired fft size.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/_kiss_fft_guts.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,164 @@ +/* +Copyright (c) 2003-2010, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" +#include <limits.h> + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#if (FIXED_POINT==32) +# define FRACBITS 31 +# define SAMPPROD int64_t +#define SAMP_MAX 2147483647 +#else +# define FRACBITS 15 +# define SAMPPROD int32_t +#define SAMP_MAX 32767 +#endif + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) + + +#ifdef KISS_FFT_USE_ALLOCA +// define this to allow use of alloca instead of malloc for temporary buffers +// Temporary buffers are used in two case: +// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 +// 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. +#include <alloca.h> +#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) +#define KISS_FFT_TMP_FREE(ptr) +#else +#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) +#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/kiss_fft.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,408 @@ +/* +Copyright (c) 2003-2010, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "_kiss_fft_guts.h" +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1 = st->twiddles; + kiss_fft_cpx t; + Fout2 = Fout + m; + do{ + C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2); + + C_MUL (t, *Fout2 , *tw1); + tw1 += fstride; + C_SUB( *Fout2 , *Fout , t ); + C_ADDTO( *Fout , t ); + ++Fout2; + ++Fout; + }while (--m); +} + +static void kf_bfly4( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + const size_t m + ) +{ + kiss_fft_cpx *tw1,*tw2,*tw3; + kiss_fft_cpx scratch[6]; + size_t k=m; + const size_t m2=2*m; + const size_t m3=3*m; + + + tw3 = tw2 = tw1 = st->twiddles; + + do { + C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4); + + C_MUL(scratch[0],Fout[m] , *tw1 ); + C_MUL(scratch[1],Fout[m2] , *tw2 ); + C_MUL(scratch[2],Fout[m3] , *tw3 ); + + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + if(st->inverse) { + Fout[m].r = scratch[5].r - scratch[4].i; + Fout[m].i = scratch[5].i + scratch[4].r; + Fout[m3].r = scratch[5].r + scratch[4].i; + Fout[m3].i = scratch[5].i - scratch[4].r; + }else{ + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + } + ++Fout; + }while(--k); +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; u<m; ++u ) { + C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); + scratch[0] = *Fout0; + + C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); + C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); + C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); + C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); + + C_ADD( scratch[7],scratch[1],scratch[4]); + C_SUB( scratch[10],scratch[1],scratch[4]); + C_ADD( scratch[8],scratch[2],scratch[3]); + C_SUB( scratch[9],scratch[2],scratch[3]); + + Fout0->r += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + int Norig = st->nfft; + + kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p); + + for ( u=0; u<m; ++u ) { + k=u; + for ( q1=0 ; q1<p ; ++q1 ) { + scratch[q1] = Fout[ k ]; + C_FIXDIV(scratch[q1],p); + k += m; + } + + k=u; + for ( q1=0 ; q1<p ; ++q1 ) { + int twidx=0; + Fout[ k ] = scratch[0]; + for (q=1;q<p;++q ) { + twidx += fstride * k; + if (twidx>=Norig) twidx-=Norig; + C_MUL(t,scratch[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + KISS_FFT_TMP_FREE(scratch); +} + +static +void kf_work( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + kiss_fft_cpx * Fout_beg=Fout; + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + const kiss_fft_cpx * Fout_end = Fout + p*m; + +#ifdef _OPENMP + // use openmp extensions at the + // top-level (not recursive) + if (fstride==1 && p<=5) + { + int k; + + // execute the p different work units in different threads +# pragma omp parallel for + for (k=0;k<p;++k) + kf_work( Fout +k*m, f+ fstride*in_stride*k,fstride*p,in_stride,factors,st); + // all threads have joined by this point + + switch (p) { + case 2: kf_bfly2(Fout,fstride,st,m); break; + case 3: kf_bfly3(Fout,fstride,st,m); break; + case 4: kf_bfly4(Fout,fstride,st,m); break; + case 5: kf_bfly5(Fout,fstride,st,m); break; + default: kf_bfly_generic(Fout,fstride,st,m,p); break; + } + return; + } +#endif + + if (m==1) { + do{ + *Fout = *f; + f += fstride*in_stride; + }while(++Fout != Fout_end ); + }else{ + do{ + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work( Fout , f, fstride*p, in_stride, factors,st); + f += fstride*in_stride; + }while( (Fout += m) != Fout_end ); + } + + Fout=Fout_beg; + + // recombine the p smaller DFTs + switch (p) { + case 2: kf_bfly2(Fout,fstride,st,m); break; + case 3: kf_bfly3(Fout,fstride,st,m); break; + case 4: kf_bfly4(Fout,fstride,st,m); break; + case 5: kf_bfly5(Fout,fstride,st,m); break; + default: kf_bfly_generic(Fout,fstride,st,m,p); break; + } +} + +/* facbuf is populated by p1,m1,p2,m2, ... + where + p[i] * m[i] = m[i-1] + m0 = n */ +static +void kf_factor(int n,int * facbuf) +{ + int p=4; + double floor_sqrt; + floor_sqrt = floor( sqrt((double)n) ); + + /*factor out powers of 4, powers of 2, then any remaining primes */ + do { + while (n % p) { + switch (p) { + case 4: p = 2; break; + case 2: p = 3; break; + default: p += 2; break; + } + if (p > floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} + +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; + + for (i=0;i<nfft;++i) { + const double pi=3.141592653589793238462643383279502884197169399375105820974944; + double phase = -2*pi*i / nfft; + if (st->inverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } + + kf_factor(nfft,st->factors); + } + return st; +} + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft); + KISS_FFT_TMP_FREE(tmpbuf); + }else{ + kf_work( fout, fin, 1,in_stride, st->factors,st ); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + + +void kiss_fft_cleanup(void) +{ + // nothing needed any more +} + +int kiss_fft_next_fast_size(int n) +{ + while(1) { + int m=n; + while ( (m%2) == 0 ) m/=2; + while ( (m%3) == 0 ) m/=3; + while ( (m%5) == 0 ) m/=5; + if (m<=1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/kiss_fft.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,124 @@ +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include <xmmintrin.h> +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) +#define KISS_FFT_FREE _mm_free +#else +#define KISS_FFT_MALLOC malloc +#define KISS_FFT_FREE free +#endif + + +#ifdef FIXED_POINT +#include <sys/types.h> +# if (FIXED_POINT == 32) +# define kiss_fft_scalar int32_t +# else +# define kiss_fft_scalar int16_t +# endif +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free free + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +/* + * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) + */ +int kiss_fft_next_fast_size(int n); + +/* for real ffts, we need an even size */ +#define kiss_fftr_next_fast_size_real(n) \ + (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) + +#ifdef __cplusplus +} +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/kissfft.hh Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,300 @@ +#ifndef KISSFFT_CLASS_HH +#define KISSFFT_CLASS_HH +#include <complex> +#include <vector> + +namespace kissfft_utils { + +template <typename T_scalar> +struct traits +{ + typedef T_scalar scalar_type; + typedef std::complex<scalar_type> cpx_type; + void fill_twiddles( std::complex<T_scalar> * dst ,int nfft,bool inverse) + { + T_scalar phinc = (inverse?2:-2)* acos( (T_scalar) -1) / nfft; + for (int i=0;i<nfft;++i) + dst[i] = exp( std::complex<T_scalar>(0,i*phinc) ); + } + + void prepare( + std::vector< std::complex<T_scalar> > & dst, + int nfft,bool inverse, + std::vector<int> & stageRadix, + std::vector<int> & stageRemainder ) + { + _twiddles.resize(nfft); + fill_twiddles( &_twiddles[0],nfft,inverse); + dst = _twiddles; + + //factorize + //start factoring out 4's, then 2's, then 3,5,7,9,... + int n= nfft; + int p=4; + do { + while (n % p) { + switch (p) { + case 4: p = 2; break; + case 2: p = 3; break; + default: p += 2; break; + } + if (p*p>n) + p=n;// no more factors + } + n /= p; + stageRadix.push_back(p); + stageRemainder.push_back(n); + }while(n>1); + } + std::vector<cpx_type> _twiddles; + + + const cpx_type twiddle(int i) { return _twiddles[i]; } +}; + +} + +template <typename T_Scalar, + typename T_traits=kissfft_utils::traits<T_Scalar> + > +class kissfft +{ + public: + typedef T_traits traits_type; + typedef typename traits_type::scalar_type scalar_type; + typedef typename traits_type::cpx_type cpx_type; + + kissfft(int nfft,bool inverse,const traits_type & traits=traits_type() ) + :_nfft(nfft),_inverse(inverse),_traits(traits) + { + _traits.prepare(_twiddles, _nfft,_inverse ,_stageRadix, _stageRemainder); + } + + void transform(const cpx_type * src , cpx_type * dst) + { + kf_work(0, dst, src, 1,1); + } + + private: + void kf_work( int stage,cpx_type * Fout, const cpx_type * f, size_t fstride,size_t in_stride) + { + int p = _stageRadix[stage]; + int m = _stageRemainder[stage]; + cpx_type * Fout_beg = Fout; + cpx_type * Fout_end = Fout + p*m; + + if (m==1) { + do{ + *Fout = *f; + f += fstride*in_stride; + }while(++Fout != Fout_end ); + }else{ + do{ + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work(stage+1, Fout , f, fstride*p,in_stride); + f += fstride*in_stride; + }while( (Fout += m) != Fout_end ); + } + + Fout=Fout_beg; + + // recombine the p smaller DFTs + switch (p) { + case 2: kf_bfly2(Fout,fstride,m); break; + case 3: kf_bfly3(Fout,fstride,m); break; + case 4: kf_bfly4(Fout,fstride,m); break; + case 5: kf_bfly5(Fout,fstride,m); break; + default: kf_bfly_generic(Fout,fstride,m,p); break; + } + } + + // these were #define macros in the original kiss_fft + void C_ADD( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a+b;} + void C_MUL( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a*b;} + void C_SUB( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a-b;} + void C_ADDTO( cpx_type & c,const cpx_type & a) { c+=a;} + void C_FIXDIV( cpx_type & ,int ) {} // NO-OP for float types + scalar_type S_MUL( const scalar_type & a,const scalar_type & b) { return a*b;} + scalar_type HALF_OF( const scalar_type & a) { return a*.5;} + void C_MULBYSCALAR(cpx_type & c,const scalar_type & a) {c*=a;} + + void kf_bfly2( cpx_type * Fout, const size_t fstride, int m) + { + for (int k=0;k<m;++k) { + cpx_type t = Fout[m+k] * _traits.twiddle(k*fstride); + Fout[m+k] = Fout[k] - t; + Fout[k] += t; + } + } + + void kf_bfly4( cpx_type * Fout, const size_t fstride, const size_t m) + { + cpx_type scratch[7]; + int negative_if_inverse = _inverse * -2 +1; + for (size_t k=0;k<m;++k) { + scratch[0] = Fout[k+m] * _traits.twiddle(k*fstride); + scratch[1] = Fout[k+2*m] * _traits.twiddle(k*fstride*2); + scratch[2] = Fout[k+3*m] * _traits.twiddle(k*fstride*3); + scratch[5] = Fout[k] - scratch[1]; + + Fout[k] += scratch[1]; + scratch[3] = scratch[0] + scratch[2]; + scratch[4] = scratch[0] - scratch[2]; + scratch[4] = cpx_type( scratch[4].imag()*negative_if_inverse , -scratch[4].real()* negative_if_inverse ); + + Fout[k+2*m] = Fout[k] - scratch[3]; + Fout[k] += scratch[3]; + Fout[k+m] = scratch[5] + scratch[4]; + Fout[k+3*m] = scratch[5] - scratch[4]; + } + } + + void kf_bfly3( cpx_type * Fout, const size_t fstride, const size_t m) + { + size_t k=m; + const size_t m2 = 2*m; + cpx_type *tw1,*tw2; + cpx_type scratch[5]; + cpx_type epi3; + epi3 = _twiddles[fstride*m]; + + tw1=tw2=&_twiddles[0]; + + do{ + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m] = cpx_type( Fout->real() - HALF_OF(scratch[3].real() ) , Fout->imag() - HALF_OF(scratch[3].imag() ) ); + + C_MULBYSCALAR( scratch[0] , epi3.imag() ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2] = cpx_type( Fout[m].real() + scratch[0].imag() , Fout[m].imag() - scratch[0].real() ); + + C_ADDTO( Fout[m] , cpx_type( -scratch[0].imag(),scratch[0].real() ) ); + ++Fout; + }while(--k); + } + + void kf_bfly5( cpx_type * Fout, const size_t fstride, const size_t m) + { + cpx_type *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + size_t u; + cpx_type scratch[13]; + cpx_type * twiddles = &_twiddles[0]; + cpx_type *tw; + cpx_type ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=twiddles; + for ( u=0; u<m; ++u ) { + C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); + scratch[0] = *Fout0; + + C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); + C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); + C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); + C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); + + C_ADD( scratch[7],scratch[1],scratch[4]); + C_SUB( scratch[10],scratch[1],scratch[4]); + C_ADD( scratch[8],scratch[2],scratch[3]); + C_SUB( scratch[9],scratch[2],scratch[3]); + + C_ADDTO( *Fout0, scratch[7]); + C_ADDTO( *Fout0, scratch[8]); + + scratch[5] = scratch[0] + cpx_type( + S_MUL(scratch[7].real(),ya.real() ) + S_MUL(scratch[8].real() ,yb.real() ), + S_MUL(scratch[7].imag(),ya.real()) + S_MUL(scratch[8].imag(),yb.real()) + ); + + scratch[6] = cpx_type( + S_MUL(scratch[10].imag(),ya.imag()) + S_MUL(scratch[9].imag(),yb.imag()), + -S_MUL(scratch[10].real(),ya.imag()) - S_MUL(scratch[9].real(),yb.imag()) + ); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11] = scratch[0] + + cpx_type( + S_MUL(scratch[7].real(),yb.real()) + S_MUL(scratch[8].real(),ya.real()), + S_MUL(scratch[7].imag(),yb.real()) + S_MUL(scratch[8].imag(),ya.real()) + ); + + scratch[12] = cpx_type( + -S_MUL(scratch[10].imag(),yb.imag()) + S_MUL(scratch[9].imag(),ya.imag()), + S_MUL(scratch[10].real(),yb.imag()) - S_MUL(scratch[9].real(),ya.imag()) + ); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } + } + + /* perform the butterfly for one stage of a mixed radix FFT */ + void kf_bfly_generic( + cpx_type * Fout, + const size_t fstride, + int m, + int p + ) + { + int u,k,q1,q; + cpx_type * twiddles = &_twiddles[0]; + cpx_type t; + int Norig = _nfft; + cpx_type scratchbuf[p]; + + for ( u=0; u<m; ++u ) { + k=u; + for ( q1=0 ; q1<p ; ++q1 ) { + scratchbuf[q1] = Fout[ k ]; + C_FIXDIV(scratchbuf[q1],p); + k += m; + } + + k=u; + for ( q1=0 ; q1<p ; ++q1 ) { + int twidx=0; + Fout[ k ] = scratchbuf[0]; + for (q=1;q<p;++q ) { + twidx += fstride * k; + if (twidx>=Norig) twidx-=Norig; + C_MUL(t,scratchbuf[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + } + + int _nfft; + bool _inverse; + std::vector<cpx_type> _twiddles; + std::vector<int> _stageRadix; + std::vector<int> _stageRemainder; + traits_type _traits; +}; +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/Makefile Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,108 @@ + +WARNINGS=-W -Wall -Wstrict-prototypes -Wmissing-prototypes -Waggregate-return \ + -Wcast-align -Wcast-qual -Wnested-externs -Wshadow -Wbad-function-cast \ + -Wwrite-strings + +CFLAGS=-O3 -I.. -I../tools $(WARNINGS) +CFLAGS+=-ffast-math -fomit-frame-pointer +#CFLAGS+=-funroll-loops +#CFLAGS+=-march=prescott +#CFLAGS+= -mtune=native +# TIP: try adding -openmp or -fopenmp to enable OPENMP directives and use of multiple cores +#CFLAGS+=-fopenmp +CFLAGS+= $(CFLAGADD) + + +ifeq "$(NFFT)" "" + NFFT=1800 +endif +ifeq "$(NUMFFTS)" "" + NUMFFTS=10000 +endif + +ifeq "$(DATATYPE)" "" + DATATYPE=float +endif + +BENCHKISS=bm_kiss_$(DATATYPE) +BENCHFFTW=bm_fftw_$(DATATYPE) +SELFTEST=st_$(DATATYPE) +TESTREAL=tr_$(DATATYPE) +TESTKFC=tkfc_$(DATATYPE) +FASTFILTREAL=ffr_$(DATATYPE) +SELFTESTSRC=twotonetest.c + + +TYPEFLAGS=-Dkiss_fft_scalar=$(DATATYPE) + +ifeq "$(DATATYPE)" "int16_t" + TYPEFLAGS=-DFIXED_POINT=16 +endif + +ifeq "$(DATATYPE)" "int32_t" + TYPEFLAGS=-DFIXED_POINT=32 +endif + +ifeq "$(DATATYPE)" "simd" + TYPEFLAGS=-DUSE_SIMD=1 -msse +endif + + +ifeq "$(DATATYPE)" "float" + # fftw needs to be built with --enable-float to build this lib + FFTWLIB=-lfftw3f +else + FFTWLIB=-lfftw3 +endif + +FFTWLIBDIR=-L/usr/local/lib/ + +SRCFILES=../kiss_fft.c ../tools/kiss_fftnd.c ../tools/kiss_fftr.c pstats.c ../tools/kfc.c ../tools/kiss_fftndr.c + +all: tools $(BENCHKISS) $(SELFTEST) $(BENCHFFTW) $(TESTREAL) $(TESTKFC) + +tools: + cd ../tools && make all + + +$(SELFTEST): $(SELFTESTSRC) $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(TESTKFC): $(SRCFILES) + $(CC) -o $@ $(CFLAGS) -DKFC_TEST $(TYPEFLAGS) $+ -lm + +$(TESTREAL): test_real.c $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(BENCHKISS): benchkiss.c $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(BENCHFFTW): benchfftw.c pstats.c + @echo "======attempting to build FFTW benchmark" + @$(CC) -o $@ $(CFLAGS) -DDATATYPE$(DATATYPE) $+ $(FFTWLIB) $(FFTWLIBDIR) -lm || echo "FFTW not available for comparison" + +test: all + @./$(TESTKFC) + @echo "======1d & 2-d complex fft self test (type= $(DATATYPE) )" + @./$(SELFTEST) + @echo "======real FFT (type= $(DATATYPE) )" + @./$(TESTREAL) + @echo "======timing test (type=$(DATATYPE))" + @./$(BENCHKISS) -x $(NUMFFTS) -n $(NFFT) + @[ -x ./$(BENCHFFTW) ] && ./$(BENCHFFTW) -x $(NUMFFTS) -n $(NFFT) ||true + @echo "======higher dimensions type=$(DATATYPE))" + @./testkiss.py + +selftest.c: + ./mk_test.py 10 12 14 > selftest.c +selftest_short.c: + ./mk_test.py -s 10 12 14 > selftest_short.c + + +CXXFLAGS=-O3 -ffast-math -fomit-frame-pointer -I.. -I../tools -W -Wall +testcpp: testcpp.cc ../kissfft.hh + $(CXX) -o $@ $(CXXFLAGS) testcpp.cc -lm + + +clean: + rm -f *~ bm_* st_* tr_* kf_* tkfc_* ff_* ffr_* *.pyc *.pyo *.dat testcpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/benchfftw.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,94 @@ +#include <stdio.h> +#include <stdlib.h> +#include <fftw3.h> +#include <unistd.h> +#include "pstats.h" + +#ifdef DATATYPEdouble + +#define CPXTYPE fftw_complex +#define PLAN fftw_plan +#define FFTMALLOC fftw_malloc +#define MAKEPLAN fftw_plan_dft_1d +#define DOFFT fftw_execute +#define DESTROYPLAN fftw_destroy_plan +#define FFTFREE fftw_free + +#elif defined(DATATYPEfloat) + +#define CPXTYPE fftwf_complex +#define PLAN fftwf_plan +#define FFTMALLOC fftwf_malloc +#define MAKEPLAN fftwf_plan_dft_1d +#define DOFFT fftwf_execute +#define DESTROYPLAN fftwf_destroy_plan +#define FFTFREE fftwf_free + +#endif + +#ifndef CPXTYPE +int main(void) +{ + fprintf(stderr,"Datatype not available in FFTW\n" ); + return 0; +} +#else +int main(int argc,char ** argv) +{ + int nfft=1024; + int isinverse=0; + int numffts=1000,i; + + CPXTYPE * in=NULL; + CPXTYPE * out=NULL; + PLAN p; + + pstats_init(); + + while (1) { + int c = getopt (argc, argv, "n:ix:h"); + if (c == -1) + break; + switch (c) { + case 'n': + nfft = atoi (optarg); + break; + case 'x': + numffts = atoi (optarg); + break; + case 'i': + isinverse = 1; + break; + case 'h': + case '?': + default: + fprintf(stderr,"options:\n-n N: complex fft length\n-i: inverse\n-x N: number of ffts to compute\n" + ""); + } + } + + in=FFTMALLOC(sizeof(CPXTYPE) * nfft); + out=FFTMALLOC(sizeof(CPXTYPE) * nfft); + for (i=0;i<nfft;++i ) { + in[i][0] = rand() - RAND_MAX/2; + in[i][1] = rand() - RAND_MAX/2; + } + + if ( isinverse ) + p = MAKEPLAN(nfft, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); + else + p = MAKEPLAN(nfft, in, out, FFTW_FORWARD, FFTW_ESTIMATE); + + for (i=0;i<numffts;++i) + DOFFT(p); + + DESTROYPLAN(p); + + FFTFREE(in); FFTFREE(out); + + fprintf(stderr,"fftw\tnfft=%d\tnumffts=%d\n", nfft,numffts); + pstats_report(); + + return 0; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/benchkiss.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,122 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/times.h> +#include <unistd.h> +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include "kiss_fftnd.h" +#include "kiss_fftndr.h" + +#include "pstats.h" + +static +int getdims(int * dims, char * arg) +{ + char *s; + int ndims=0; + while ( (s=strtok( arg , ",") ) ) { + dims[ndims++] = atoi(s); + //printf("%s=%d\n",s,dims[ndims-1]); + arg=NULL; + } + return ndims; +} + +int main(int argc,char ** argv) +{ + int k; + int nfft[32]; + int ndims = 1; + int isinverse=0; + int numffts=1000,i; + kiss_fft_cpx * buf; + kiss_fft_cpx * bufout; + int real = 0; + + nfft[0] = 1024;// default + + while (1) { + int c = getopt (argc, argv, "n:ix:r"); + if (c == -1) + break; + switch (c) { + case 'r': + real = 1; + break; + case 'n': + ndims = getdims(nfft, optarg ); + if (nfft[0] != kiss_fft_next_fast_size(nfft[0]) ) { + int ng = kiss_fft_next_fast_size(nfft[0]); + fprintf(stderr,"warning: %d might be a better choice for speed than %d\n",ng,nfft[0]); + } + break; + case 'x': + numffts = atoi (optarg); + break; + case 'i': + isinverse = 1; + break; + } + } + int nbytes = sizeof(kiss_fft_cpx); + for (k=0;k<ndims;++k) + nbytes *= nfft[k]; + +#ifdef USE_SIMD + numffts /= 4; + fprintf(stderr,"since SIMD implementation does 4 ffts at a time, numffts is being reduced to %d\n",numffts); +#endif + + buf=(kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); + bufout=(kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); + memset(buf,0,nbytes); + + pstats_init(); + + if (ndims==1) { + if (real) { + kiss_fftr_cfg st = kiss_fftr_alloc( nfft[0] ,isinverse ,0,0); + if (isinverse) + for (i=0;i<numffts;++i) + kiss_fftri( st ,(kiss_fft_cpx*)buf,(kiss_fft_scalar*)bufout ); + else + for (i=0;i<numffts;++i) + kiss_fftr( st ,(kiss_fft_scalar*)buf,(kiss_fft_cpx*)bufout ); + free(st); + }else{ + kiss_fft_cfg st = kiss_fft_alloc( nfft[0] ,isinverse ,0,0); + for (i=0;i<numffts;++i) + kiss_fft( st ,buf,bufout ); + free(st); + } + }else{ + if (real) { + kiss_fftndr_cfg st = kiss_fftndr_alloc( nfft,ndims ,isinverse ,0,0); + if (isinverse) + for (i=0;i<numffts;++i) + kiss_fftndri( st ,(kiss_fft_cpx*)buf,(kiss_fft_scalar*)bufout ); + else + for (i=0;i<numffts;++i) + kiss_fftndr( st ,(kiss_fft_scalar*)buf,(kiss_fft_cpx*)bufout ); + free(st); + }else{ + kiss_fftnd_cfg st= kiss_fftnd_alloc(nfft,ndims,isinverse ,0,0); + for (i=0;i<numffts;++i) + kiss_fftnd( st ,buf,bufout ); + free(st); + } + } + + free(buf); free(bufout); + + fprintf(stderr,"KISS\tnfft="); + for (k=0;k<ndims;++k) + fprintf(stderr, "%d,",nfft[k]); + fprintf(stderr,"\tnumffts=%d\n" ,numffts); + pstats_report(); + + kiss_fft_cleanup(); + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/compfft.py Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# use FFTPACK as a baseline +import FFT +from Numeric import * +import math +import random +import sys +import struct +import fft + +pi=math.pi +e=math.e +j=complex(0,1) +lims=(-32768,32767) + +def randbuf(n,cpx=1): + res = array( [ random.uniform( lims[0],lims[1] ) for i in range(n) ] ) + if cpx: + res = res + j*randbuf(n,0) + return res + +def main(): + from getopt import getopt + import popen2 + opts,args = getopt( sys.argv[1:],'u:n:Rt:' ) + opts=dict(opts) + exitcode=0 + + util = opts.get('-u','./kf_float') + + try: + dims = [ int(d) for d in opts['-n'].split(',')] + cpx = opts.get('-R') is None + fmt=opts.get('-t','f') + except KeyError: + sys.stderr.write(""" + usage: compfft.py + -n d1[,d2,d3...] : FFT dimension(s) + -u utilname : see sample_code/fftutil.c, default = ./kf_float + -R : real-optimized version\n""") + sys.exit(1) + + x = fft.make_random( dims ) + + cmd = '%s -n %s ' % ( util, ','.join([ str(d) for d in dims]) ) + if cpx: + xout = FFT.fftnd(x) + xout = reshape(xout,(size(xout),)) + else: + cmd += '-R ' + xout = FFT.real_fft(x) + + proc = popen2.Popen3( cmd , bufsize=len(x) ) + + proc.tochild.write( dopack( x , fmt ,cpx ) ) + proc.tochild.close() + xoutcomp = dounpack( proc.fromchild.read( ) , fmt ,1 ) + #xoutcomp = reshape( xoutcomp , dims ) + + sig = xout * conjugate(xout) + sigpow = sum( sig ) + + diff = xout-xoutcomp + noisepow = sum( diff * conjugate(diff) ) + + snr = 10 * math.log10(abs( sigpow / noisepow ) ) + if snr<100: + print xout + print xoutcomp + exitcode=1 + print 'NFFT=%s,SNR = %f dB' % (str(dims),snr) + sys.exit(exitcode) + +def dopack(x,fmt,cpx): + x = reshape( x, ( size(x),) ) + if cpx: + s = ''.join( [ struct.pack('ff',c.real,c.imag) for c in x ] ) + else: + s = ''.join( [ struct.pack('f',c) for c in x ] ) + return s + +def dounpack(x,fmt,cpx): + uf = fmt * ( len(x) / 4 ) + s = struct.unpack(uf,x) + if cpx: + return array(s[::2]) + array( s[1::2] )*j + else: + return array(s ) + +if __name__ == "__main__": + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/doit.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,129 @@ +/* this program is in the public domain + A program that helps the authors of the fine fftw library benchmark kiss +*/ + +#include "bench-user.h" +#include <math.h> + +#include "kiss_fft.h" +#include "kiss_fftnd.h" +#include "kiss_fftr.h" + +BEGIN_BENCH_DOC +BENCH_DOC("name", "kissfft") +BENCH_DOC("version", "1.0.1") +BENCH_DOC("year", "2004") +BENCH_DOC("author", "Mark Borgerding") +BENCH_DOC("language", "C") +BENCH_DOC("url", "http://sourceforge.net/projects/kissfft/") +BENCH_DOC("copyright", +"Copyright (c) 2003,4 Mark Borgerding\n" +"\n" +"All rights reserved.\n" +"\n" +"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n" +"\n" +" * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n" +" * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n" +" * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n" +"\n" + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n") +END_BENCH_DOC + +int can_do(struct problem *p) +{ + if (p->rank == 1) { + if (p->kind == PROBLEM_REAL) { + return (p->n[0] & 1) == 0; /* only even real is okay */ + } else { + return 1; + } + } else { + return p->kind == PROBLEM_COMPLEX; + } +} + +static kiss_fft_cfg cfg=NULL; +static kiss_fftr_cfg cfgr=NULL; +static kiss_fftnd_cfg cfgnd=NULL; + +#define FAILIF( c ) \ + if ( c ) do {\ + fprintf(stderr,\ + "kissfft: " #c " (file=%s:%d errno=%d %s)\n",\ + __FILE__,__LINE__ , errno,strerror( errno ) ) ;\ + exit(1);\ + }while(0) + + + +void setup(struct problem *p) +{ + size_t i; + + /* + fprintf(stderr,"%s %s %d-d ", + (p->sign == 1)?"Inverse":"Forward", + p->kind == PROBLEM_COMPLEX?"Complex":"Real", + p->rank); + */ + if (p->rank == 1) { + if (p->kind == PROBLEM_COMPLEX) { + cfg = kiss_fft_alloc (p->n[0], (p->sign == 1), 0, 0); + FAILIF(cfg==NULL); + }else{ + cfgr = kiss_fftr_alloc (p->n[0], (p->sign == 1), 0, 0); + FAILIF(cfgr==NULL); + } + }else{ + int dims[5]; + for (i=0;i<p->rank;++i){ + dims[i] = p->n[i]; + } + /* multi-dimensional */ + if (p->kind == PROBLEM_COMPLEX) { + cfgnd = kiss_fftnd_alloc( dims , p->rank, (p->sign == 1), 0, 0 ); + FAILIF(cfgnd==NULL); + } + } +} + +void doit(int iter, struct problem *p) +{ + int i; + void *in = p->in; + void *out = p->out; + + if (p->in_place) + out = p->in; + + if (p->rank == 1) { + if (p->kind == PROBLEM_COMPLEX){ + for (i = 0; i < iter; ++i) + kiss_fft (cfg, in, out); + } else { + /* PROBLEM_REAL */ + if (p->sign == -1) /* FORWARD */ + for (i = 0; i < iter; ++i) + kiss_fftr (cfgr, in, out); + else + for (i = 0; i < iter; ++i) + kiss_fftri (cfgr, in, out); + } + }else{ + /* multi-dimensional */ + for (i = 0; i < iter; ++i) + kiss_fftnd(cfgnd,in,out); + } +} + +void done(struct problem *p) +{ + free(cfg); + cfg=NULL; + free(cfgr); + cfgr=NULL; + free(cfgnd); + cfgnd=NULL; + UNUSED(p); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/fastfir.py Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +from Numeric import * +from FFT import * + +def make_random(len): + import random + res=[] + for i in range(int(len)): + r=random.uniform(-1,1) + i=random.uniform(-1,1) + res.append( complex(r,i) ) + return res + +def slowfilter(sig,h): + translen = len(h)-1 + return convolve(sig,h)[translen:-translen] + +def nextpow2(x): + return 2 ** math.ceil(math.log(x)/math.log(2)) + +def fastfilter(sig,h,nfft=None): + if nfft is None: + nfft = int( nextpow2( 2*len(h) ) ) + H = fft( h , nfft ) + scraplen = len(h)-1 + keeplen = nfft-scraplen + res=[] + isdone = 0 + lastidx = nfft + idx0 = 0 + while not isdone: + idx1 = idx0 + nfft + if idx1 >= len(sig): + idx1 = len(sig) + lastidx = idx1-idx0 + if lastidx <= scraplen: + break + isdone = 1 + Fss = fft(sig[idx0:idx1],nfft) + fm = Fss * H + m = inverse_fft(fm) + res.append( m[scraplen:lastidx] ) + idx0 += keeplen + return concatenate( res ) + +def main(): + import sys + from getopt import getopt + opts,args = getopt(sys.argv[1:],'rn:l:') + opts=dict(opts) + + siglen = int(opts.get('-l',1e4 ) ) + hlen =50 + + nfft = int(opts.get('-n',128) ) + usereal = opts.has_key('-r') + + print 'nfft=%d'%nfft + # make a signal + sig = make_random( siglen ) + # make an impulse response + h = make_random( hlen ) + #h=[1]*2+[0]*3 + if usereal: + sig=[c.real for c in sig] + h=[c.real for c in h] + + # perform MAC filtering + yslow = slowfilter(sig,h) + #print '<YSLOW>',yslow,'</YSLOW>' + #yfast = fastfilter(sig,h,nfft) + yfast = utilfastfilter(sig,h,nfft,usereal) + #print yfast + print 'len(yslow)=%d'%len(yslow) + print 'len(yfast)=%d'%len(yfast) + diff = yslow-yfast + snr = 10*log10( abs( vdot(yslow,yslow) / vdot(diff,diff) ) ) + print 'snr=%s' % snr + if snr < 10.0: + print 'h=',h + print 'sig=',sig[:5],'...' + print 'yslow=',yslow[:5],'...' + print 'yfast=',yfast[:5],'...' + +def utilfastfilter(sig,h,nfft,usereal): + import compfft + import os + open( 'sig.dat','w').write( compfft.dopack(sig,'f',not usereal) ) + open( 'h.dat','w').write( compfft.dopack(h,'f',not usereal) ) + if usereal: + util = './fastconvr' + else: + util = './fastconv' + cmd = 'time %s -n %d -i sig.dat -h h.dat -o out.dat' % (util, nfft) + print cmd + ec = os.system(cmd) + print 'exited->',ec + return compfft.dounpack(open('out.dat').read(),'f',not usereal) + +if __name__ == "__main__": + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/fft.py Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +import math +import sys +import random + +pi=math.pi +e=math.e +j=complex(0,1) + +def fft(f,inv): + n=len(f) + if n==1: + return f + + for p in 2,3,5: + if n%p==0: + break + else: + raise Exception('%s not factorable ' % n) + + m = n/p + Fout=[] + for q in range(p): # 0,1 + fp = f[q::p] # every p'th time sample + Fp = fft( fp ,inv) + Fout.extend( Fp ) + + for u in range(m): + scratch = Fout[u::m] # u to end in strides of m + for q1 in range(p): + k = q1*m + u # indices to Fout above that became scratch + Fout[ k ] = scratch[0] # cuz e**0==1 in loop below + for q in range(1,p): + if inv: + t = e ** ( j*2*pi*k*q/n ) + else: + t = e ** ( -j*2*pi*k*q/n ) + Fout[ k ] += scratch[q] * t + + return Fout + +def rifft(F): + N = len(F) - 1 + Z = [0] * (N) + for k in range(N): + Fek = ( F[k] + F[-k-1].conjugate() ) + Fok = ( F[k] - F[-k-1].conjugate() ) * e ** (j*pi*k/N) + Z[k] = Fek + j*Fok + + fp = fft(Z , 1) + + f = [] + for c in fp: + f.append(c.real) + f.append(c.imag) + return f + +def real_fft( f,inv ): + if inv: + return rifft(f) + + N = len(f) / 2 + + res = f[::2] + ims = f[1::2] + + fp = [ complex(r,i) for r,i in zip(res,ims) ] + print 'fft input ', fp + Fp = fft( fp ,0 ) + print 'fft output ', Fp + + F = [ complex(0,0) ] * ( N+1 ) + + F[0] = complex( Fp[0].real + Fp[0].imag , 0 ) + + for k in range(1,N/2+1): + tw = e ** ( -j*pi*(.5+float(k)/N ) ) + + F1k = Fp[k] + Fp[N-k].conjugate() + F2k = Fp[k] - Fp[N-k].conjugate() + F2k *= tw + F[k] = ( F1k + F2k ) * .5 + F[N-k] = ( F1k - F2k ).conjugate() * .5 + #F[N-k] = ( F1kp + e ** ( -j*pi*(.5+float(N-k)/N ) ) * F2kp ) * .5 + #F[N-k] = ( F1k.conjugate() - tw.conjugate() * F2k.conjugate() ) * .5 + + F[N] = complex( Fp[0].real - Fp[0].imag , 0 ) + return F + +def main(): + #fft_func = fft + fft_func = real_fft + + tvec = [0.309655,0.815653,0.768570,0.591841,0.404767,0.637617,0.007803,0.012665] + Ftvec = [ complex(r,i) for r,i in zip( + [3.548571,-0.378761,-0.061950,0.188537,-0.566981,0.188537,-0.061950,-0.378761], + [0.000000,-1.296198,-0.848764,0.225337,0.000000,-0.225337,0.848764,1.296198] ) ] + + F = fft_func( tvec,0 ) + + nerrs= 0 + for i in range(len(Ftvec)/2 + 1): + if abs( F[i] - Ftvec[i] )> 1e-5: + print 'F[%d]: %s != %s' % (i,F[i],Ftvec[i]) + nerrs += 1 + + print '%d errors in forward fft' % nerrs + if nerrs: + return + + trec = fft_func( F , 1 ) + + for i in range(len(trec) ): + trec[i] /= len(trec) + + for i in range(len(tvec) ): + if abs( trec[i] - tvec[i] )> 1e-5: + print 't[%d]: %s != %s' % (i,tvec[i],trec[i]) + nerrs += 1 + + print '%d errors in reverse fft' % nerrs + + +def make_random(dims=[1]): + import Numeric + res = [] + for i in range(dims[0]): + if len(dims)==1: + r=random.uniform(-1,1) + i=random.uniform(-1,1) + res.append( complex(r,i) ) + else: + res.append( make_random( dims[1:] ) ) + return Numeric.array(res) + +def flatten(x): + import Numeric + ntotal = Numeric.product(Numeric.shape(x)) + return Numeric.reshape(x,(ntotal,)) + +def randmat( ndims ): + dims=[] + for i in range( ndims ): + curdim = int( random.uniform(2,4) ) + dims.append( curdim ) + return make_random(dims ) + +def test_fftnd(ndims=3): + import FFT + import Numeric + + x=randmat( ndims ) + print 'dimensions=%s' % str( Numeric.shape(x) ) + #print 'x=%s' %str(x) + xver = FFT.fftnd(x) + x2=myfftnd(x) + err = xver - x2 + errf = flatten(err) + xverf = flatten(xver) + errpow = Numeric.vdot(errf,errf)+1e-10 + sigpow = Numeric.vdot(xverf,xverf)+1e-10 + snr = 10*math.log10(abs(sigpow/errpow) ) + if snr<80: + print xver + print x2 + print 'SNR=%sdB' % str( snr ) + +def myfftnd(x): + import Numeric + xf = flatten(x) + Xf = fftndwork( xf , Numeric.shape(x) ) + return Numeric.reshape(Xf,Numeric.shape(x) ) + +def fftndwork(x,dims): + import Numeric + dimprod=Numeric.product( dims ) + + for k in range( len(dims) ): + cur_dim=dims[ k ] + stride=dimprod/cur_dim + next_x = [complex(0,0)]*len(x) + for i in range(stride): + next_x[i*cur_dim:(i+1)*cur_dim] = fft(x[i:(i+cur_dim)*stride:stride],0) + x = next_x + return x + +if __name__ == "__main__": + try: + nd = int(sys.argv[1]) + except: + nd=None + if nd: + test_fftnd( nd ) + else: + sys.exit(0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/mk_test.py Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import FFT +import sys +import random +import re +j=complex(0,1) + +def randvec(n,iscomplex): + if iscomplex: + return [ + int(random.uniform(-32768,32767) ) + j*int(random.uniform(-32768,32767) ) + for i in range(n) ] + else: + return [ int(random.uniform(-32768,32767) ) for i in range(n) ] + +def c_format(v,round=0): + if round: + return ','.join( [ '{%d,%d}' %(int(c.real),int(c.imag) ) for c in v ] ) + else: + s= ','.join( [ '{%.60f ,%.60f }' %(c.real,c.imag) for c in v ] ) + return re.sub(r'\.?0+ ',' ',s) + +def test_cpx( n,inverse ,short): + v = randvec(n,1) + scale = 1 + if short: + minsnr=30 + else: + minsnr=100 + + if inverse: + tvecout = FFT.inverse_fft(v) + if short: + scale = 1 + else: + scale = len(v) + else: + tvecout = FFT.fft(v) + if short: + scale = 1.0/len(v) + + tvecout = [ c * scale for c in tvecout ] + + + s="""#define NFFT %d""" % len(v) + """ + { + double snr; + kiss_fft_cpx test_vec_in[NFFT] = { """ + c_format(v) + """}; + kiss_fft_cpx test_vec_out[NFFT] = {""" + c_format( tvecout ) + """}; + kiss_fft_cpx testbuf[NFFT]; + void * cfg = kiss_fft_alloc(NFFT,%d,0,0);""" % inverse + """ + + kiss_fft(cfg,test_vec_in,testbuf); + snr = snr_compare(test_vec_out,testbuf,NFFT); + printf("DATATYPE=" xstr(kiss_fft_scalar) ", FFT n=%d, inverse=%d, snr = %g dB\\n",NFFT,""" + str(inverse) + """,snr); + if (snr<""" + str(minsnr) + """) + exit_code++; + free(cfg); + } +#undef NFFT +""" + return s + +def compare_func(): + s=""" +#define xstr(s) str(s) +#define str(s) #s +double snr_compare( kiss_fft_cpx * test_vec_out,kiss_fft_cpx * testbuf, int n) +{ + int k; + double sigpow,noisepow,err,snr,scale=0; + kiss_fft_cpx err; + sigpow = noisepow = .000000000000000000000000000001; + + for (k=0;k<n;++k) { + sigpow += test_vec_out[k].r * test_vec_out[k].r + + test_vec_out[k].i * test_vec_out[k].i; + C_SUB(err,test_vec_out[k],testbuf[k].r); + noisepow += err.r * err.r + err.i + err.i; + + if (test_vec_out[k].r) + scale += testbuf[k].r / test_vec_out[k].r; + } + snr = 10*log10( sigpow / noisepow ); + scale /= n; + if (snr<10) + printf( "\\npoor snr, try a scaling factor %f\\n" , scale ); + return snr; +} +""" + return s + +def main(): + + from getopt import getopt + opts,args = getopt(sys.argv[1:],'s') + opts = dict(opts) + short = int( opts.has_key('-s') ) + + fftsizes = args + if not fftsizes: + fftsizes = [ 1800 ] + print '#include "kiss_fft.h"' + print compare_func() + print "int main() { int exit_code=0;\n" + for n in fftsizes: + n = int(n) + print test_cpx(n,0,short) + print test_cpx(n,1,short) + print """ + return exit_code; +} +""" + +if __name__ == "__main__": + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/pstats.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,49 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/times.h> +#include <sys/types.h> +#include <unistd.h> + +#include "pstats.h" + +static struct tms tms_beg; +static struct tms tms_end; +static int has_times = 0; + + +void pstats_init(void) +{ + has_times = times(&tms_beg) != -1; +} + +static void tms_report(void) +{ + double cputime; + if (! has_times ) + return; + times(&tms_end); + cputime = ( ((float)tms_end.tms_utime + tms_end.tms_stime + tms_end.tms_cutime + tms_end.tms_cstime ) - + ((float)tms_beg.tms_utime + tms_beg.tms_stime + tms_beg.tms_cutime + tms_beg.tms_cstime ) ) + / sysconf(_SC_CLK_TCK); + fprintf(stderr,"\tcputime=%.3f\n" , cputime); +} + +static void ps_report(void) +{ + char buf[1024]; +#ifdef __APPLE__ /* MAC OS X */ + sprintf(buf,"ps -o command,majflt,minflt,rss,pagein,vsz -p %d 1>&2",getpid() ); +#else /* GNU/Linux */ + sprintf(buf,"ps -o comm,majflt,minflt,rss,drs,pagein,sz,trs,vsz %d 1>&2",getpid() ); +#endif + if (system( buf )==-1) { + perror("system call to ps failed"); + } +} + +void pstats_report() +{ + ps_report(); + tms_report(); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/pstats.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,7 @@ +#ifndef PSTATS_H +#define PSTATS_H + +void pstats_init(void); +void pstats_report(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/tailscrap.m Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,26 @@ +function maxabsdiff=tailscrap() +% test code for circular convolution with the scrapped portion +% at the tail of the buffer, rather than the front +% +% The idea is to rotate the zero-padded h (impulse response) buffer +% to the left nh-1 samples, rotating the junk samples as well. +% This could be very handy in avoiding buffer copies during fast filtering. +nh=10; +nfft=256; + +h=rand(1,nh); +x=rand(1,nfft); + +hpad=[ h(nh) zeros(1,nfft-nh) h(1:nh-1) ]; + +% baseline comparison +y1 = filter(h,1,x); +y1_notrans = y1(nh:nfft); + +% fast convolution +y2 = ifft( fft(hpad) .* fft(x) ); +y2_notrans=y2(1:nfft-nh+1); + +maxabsdiff = max(abs(y2_notrans - y1_notrans)) + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/test_real.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,172 @@ +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" +#include <sys/times.h> +#include <time.h> +#include <unistd.h> + +static double cputime(void) +{ + struct tms t; + times(&t); + return (double)(t.tms_utime + t.tms_stime)/ sysconf(_SC_CLK_TCK) ; +} + +static +kiss_fft_scalar rand_scalar(void) +{ +#ifdef USE_SIMD + return _mm_set1_ps(rand()-RAND_MAX/2); +#else + kiss_fft_scalar s = (kiss_fft_scalar)(rand() -RAND_MAX/2); + return s/2; +#endif +} + +static +double snr_compare( kiss_fft_cpx * vec1,kiss_fft_cpx * vec2, int n) +{ + int k; + double sigpow=1e-10,noisepow=1e-10,err,snr,scale=0; + +#ifdef USE_SIMD + float *fv1 = (float*)vec1; + float *fv2 = (float*)vec2; + for (k=0;k<8*n;++k) { + sigpow += *fv1 * *fv1; + err = *fv1 - *fv2; + noisepow += err*err; + ++fv1; + ++fv2; + } +#else + for (k=0;k<n;++k) { + sigpow += (double)vec1[k].r * (double)vec1[k].r + + (double)vec1[k].i * (double)vec1[k].i; + err = (double)vec1[k].r - (double)vec2[k].r; + noisepow += err * err; + err = (double)vec1[k].i - (double)vec2[k].i; + noisepow += err * err; + + if (vec1[k].r) + scale +=(double) vec2[k].r / (double)vec1[k].r; + } +#endif + snr = 10*log10( sigpow / noisepow ); + scale /= n; + if (snr<10) { + printf( "\npoor snr, try a scaling factor %f\n" , scale ); + exit(1); + } + return snr; +} + +#ifndef NUMFFTS +#define NUMFFTS 10000 +#endif + + +int main(int argc,char ** argv) +{ + int nfft = 8*3*5; + double ts,tfft,trfft; + int i; + if (argc>1) + nfft = atoi(argv[1]); + kiss_fft_cpx cin[nfft]; + kiss_fft_cpx cout[nfft]; + kiss_fft_cpx sout[nfft]; + kiss_fft_cfg kiss_fft_state; + kiss_fftr_cfg kiss_fftr_state; + + kiss_fft_scalar rin[nfft+2]; + kiss_fft_scalar rout[nfft+2]; + kiss_fft_scalar zero; + memset(&zero,0,sizeof(zero) ); // ugly way of setting short,int,float,double, or __m128 to zero + + srand(time(0)); + + for (i=0;i<nfft;++i) { + rin[i] = rand_scalar(); + cin[i].r = rin[i]; + cin[i].i = zero; + } + + kiss_fft_state = kiss_fft_alloc(nfft,0,0,0); + kiss_fftr_state = kiss_fftr_alloc(nfft,0,0,0); + kiss_fft(kiss_fft_state,cin,cout); + kiss_fftr(kiss_fftr_state,rin,sout); + /* + printf(" results from kiss_fft : (%f,%f), (%f,%f), (%f,%f) ...\n " + , (float)cout[0].r , (float)cout[0].i + , (float)cout[1].r , (float)cout[1].i + , (float)cout[2].r , (float)cout[2].i); + printf(" results from kiss_fftr: (%f,%f), (%f,%f), (%f,%f) ...\n " + , (float)sout[0].r , (float)sout[0].i + , (float)sout[1].r , (float)sout[1].i + , (float)sout[2].r , (float)sout[2].i); + */ + + printf( "nfft=%d, inverse=%d, snr=%g\n", + nfft,0, snr_compare(cout,sout,(nfft/2)+1) ); + ts = cputime(); + for (i=0;i<NUMFFTS;++i) { + kiss_fft(kiss_fft_state,cin,cout); + } + tfft = cputime() - ts; + + ts = cputime(); + for (i=0;i<NUMFFTS;++i) { + kiss_fftr( kiss_fftr_state, rin, cout ); + /* kiss_fftri(kiss_fftr_state,cout,rin); */ + } + trfft = cputime() - ts; + + printf("%d complex ffts took %gs, real took %gs\n",NUMFFTS,tfft,trfft); + + free(kiss_fft_state); + free(kiss_fftr_state); + + kiss_fft_state = kiss_fft_alloc(nfft,1,0,0); + kiss_fftr_state = kiss_fftr_alloc(nfft,1,0,0); + + memset(cin,0,sizeof(cin)); +#if 1 + for (i=1;i< nfft/2;++i) { + //cin[i].r = (kiss_fft_scalar)(rand()-RAND_MAX/2); + cin[i].r = rand_scalar(); + cin[i].i = rand_scalar(); + } +#else + cin[0].r = 12000; + cin[3].r = 12000; + cin[nfft/2].r = 12000; +#endif + + // conjugate symmetry of real signal + for (i=1;i< nfft/2;++i) { + cin[nfft-i].r = cin[i].r; + cin[nfft-i].i = - cin[i].i; + } + + kiss_fft(kiss_fft_state,cin,cout); + kiss_fftri(kiss_fftr_state,cin,rout); + /* + printf(" results from inverse kiss_fft : (%f,%f), (%f,%f), (%f,%f), (%f,%f), (%f,%f) ...\n " + , (float)cout[0].r , (float)cout[0].i , (float)cout[1].r , (float)cout[1].i , (float)cout[2].r , (float)cout[2].i , (float)cout[3].r , (float)cout[3].i , (float)cout[4].r , (float)cout[4].i + ); + + printf(" results from inverse kiss_fftr: %f,%f,%f,%f,%f ... \n" + ,(float)rout[0] ,(float)rout[1] ,(float)rout[2] ,(float)rout[3] ,(float)rout[4]); +*/ + for (i=0;i<nfft;++i) { + sout[i].r = rout[i]; + sout[i].i = zero; + } + + printf( "nfft=%d, inverse=%d, snr=%g\n", + nfft,1, snr_compare(cout,sout,nfft/2) ); + free(kiss_fft_state); + free(kiss_fftr_state); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/test_vs_dft.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,74 @@ +#include "kiss_fft.h" + + +void check(kiss_fft_cpx * in,kiss_fft_cpx * out,int nfft,int isinverse) +{ + int bin,k; + double errpow=0,sigpow=0; + + for (bin=0;bin<nfft;++bin) { + double ansr = 0; + double ansi = 0; + double difr; + double difi; + + for (k=0;k<nfft;++k) { + double phase = -2*M_PI*bin*k/nfft; + double re = cos(phase); + double im = sin(phase); + if (isinverse) + im = -im; + +#ifdef FIXED_POINT + re /= nfft; + im /= nfft; +#endif + + ansr += in[k].r * re - in[k].i * im; + ansi += in[k].r * im + in[k].i * re; + } + difr = ansr - out[bin].r; + difi = ansi - out[bin].i; + errpow += difr*difr + difi*difi; + sigpow += ansr*ansr+ansi*ansi; + } + printf("nfft=%d inverse=%d,snr = %f\n",nfft,isinverse,10*log10(sigpow/errpow) ); +} + +void test1d(int nfft,int isinverse) +{ + size_t buflen = sizeof(kiss_fft_cpx)*nfft; + + kiss_fft_cpx * in = (kiss_fft_cpx*)malloc(buflen); + kiss_fft_cpx * out= (kiss_fft_cpx*)malloc(buflen); + kiss_fft_cfg cfg = kiss_fft_alloc(nfft,isinverse,0,0); + int k; + + for (k=0;k<nfft;++k) { + in[k].r = (rand() % 65536) - 32768; + in[k].i = (rand() % 65536) - 32768; + } + + kiss_fft(cfg,in,out); + + check(in,out,nfft,isinverse); + + free(in); + free(out); + free(cfg); +} + +int main(int argc,char ** argv) +{ + if (argc>1) { + int k; + for (k=1;k<argc;++k) { + test1d(atoi(argv[k]),0); + test1d(atoi(argv[k]),1); + } + }else{ + test1d(32,0); + test1d(32,1); + } + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/testcpp.cc Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,73 @@ +#include "kissfft.hh" +#include <iostream> +#include <cstdlib> +#include <typeinfo> + +#include <sys/time.h> +static inline +double curtime(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec*.000001; +} + +using namespace std; + +template <class T> +void dotest(int nfft) +{ + typedef kissfft<T> FFT; + typedef std::complex<T> cpx_type; + + cout << "type:" << typeid(T).name() << " nfft:" << nfft; + + FFT fft(nfft,false); + + vector<cpx_type> inbuf(nfft); + vector<cpx_type> outbuf(nfft); + for (int k=0;k<nfft;++k) + inbuf[k]= cpx_type( + (T)(rand()/(double)RAND_MAX - .5), + (T)(rand()/(double)RAND_MAX - .5) ); + fft.transform( &inbuf[0] , &outbuf[0] ); + + long double totalpower=0; + long double difpower=0; + for (int k0=0;k0<nfft;++k0) { + complex<long double> acc = 0; + long double phinc = 2*k0* M_PIl / nfft; + for (int k1=0;k1<nfft;++k1) { + complex<long double> x(inbuf[k1].real(),inbuf[k1].imag()); + acc += x * exp( complex<long double>(0,-k1*phinc) ); + } + totalpower += norm(acc); + complex<long double> x(outbuf[k0].real(),outbuf[k0].imag()); + complex<long double> dif = acc - x; + difpower += norm(dif); + } + cout << " RMSE:" << sqrt(difpower/totalpower) << "\t"; + + double t0 = curtime(); + int nits=20e6/nfft; + for (int k=0;k<nits;++k) { + fft.transform( &inbuf[0] , &outbuf[0] ); + } + double t1 = curtime(); + cout << " MSPS:" << ( (nits*nfft)*1e-6/ (t1-t0) ) << endl; +} + +int main(int argc,char ** argv) +{ + if (argc>1) { + for (int k=1;k<argc;++k) { + int nfft = atoi(argv[k]); + dotest<float>(nfft); dotest<double>(nfft); dotest<long double>(nfft); + } + }else{ + dotest<float>(32); dotest<double>(32); dotest<long double>(32); + dotest<float>(1024); dotest<double>(1024); dotest<long double>(1024); + dotest<float>(840); dotest<double>(840); dotest<long double>(840); + } + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/testkiss.py Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +import math +import sys +import os +import random +import struct +import popen2 +import getopt +import numpy + +pi=math.pi +e=math.e +j=complex(0,1) + +doreal=0 + +datatype = os.environ.get('DATATYPE','float') + +util = '../tools/fft_' + datatype +minsnr=90 +if datatype == 'double': + fmt='d' +elif datatype=='int16_t': + fmt='h' + minsnr=10 +elif datatype=='int32_t': + fmt='i' +elif datatype=='simd': + fmt='4f' + sys.stderr.write('testkiss.py does not yet test simd') + sys.exit(0) +elif datatype=='float': + fmt='f' +else: + sys.stderr.write('unrecognized datatype %s\n' % datatype) + sys.exit(1) + + +def dopack(x,cpx=1): + x = numpy.reshape( x, ( numpy.size(x),) ) + + if cpx: + s = ''.join( [ struct.pack(fmt*2,c.real,c.imag) for c in x ] ) + else: + s = ''.join( [ struct.pack(fmt,c.real) for c in x ] ) + return s + +def dounpack(x,cpx): + uf = fmt * ( len(x) / struct.calcsize(fmt) ) + s = struct.unpack(uf,x) + if cpx: + return numpy.array(s[::2]) + numpy.array( s[1::2] )*j + else: + return numpy.array(s ) + +def make_random(dims=[1]): + res = [] + for i in range(dims[0]): + if len(dims)==1: + r=random.uniform(-1,1) + if doreal: + res.append( r ) + else: + i=random.uniform(-1,1) + res.append( complex(r,i) ) + else: + res.append( make_random( dims[1:] ) ) + return numpy.array(res) + +def flatten(x): + ntotal = numpy.size(x) + return numpy.reshape(x,(ntotal,)) + +def randmat( ndims ): + dims=[] + for i in range( ndims ): + curdim = int( random.uniform(2,5) ) + if doreal and i==(ndims-1): + curdim = int(curdim/2)*2 # force even last dimension if real + dims.append( curdim ) + return make_random(dims ) + +def test_fft(ndims): + x=randmat( ndims ) + + + if doreal: + xver = numpy.fft.rfftn(x) + else: + xver = numpy.fft.fftn(x) + + open('/tmp/fftexp.dat','w').write(dopack( flatten(xver) , True ) ) + + x2=dofft(x,doreal) + err = xver - x2 + errf = flatten(err) + xverf = flatten(xver) + errpow = numpy.vdot(errf,errf)+1e-10 + sigpow = numpy.vdot(xverf,xverf)+1e-10 + snr = 10*math.log10(abs(sigpow/errpow) ) + print 'SNR (compared to NumPy) : %.1fdB' % float(snr) + + if snr<minsnr: + print 'xver=',xver + print 'x2=',x2 + print 'err',err + sys.exit(1) + +def dofft(x,isreal): + dims=list( numpy.shape(x) ) + x = flatten(x) + + scale=1 + if datatype=='int16_t': + x = 32767 * x + scale = len(x) / 32767.0 + elif datatype=='int32_t': + x = 2147483647.0 * x + scale = len(x) / 2147483647.0 + + cmd='%s -n ' % util + cmd += ','.join([str(d) for d in dims]) + if doreal: + cmd += ' -R ' + + print cmd + p = popen2.Popen3(cmd ) + + open('/tmp/fftin.dat','w').write(dopack( x , isreal==False ) ) + + p.tochild.write( dopack( x , isreal==False ) ) + p.tochild.close() + + res = dounpack( p.fromchild.read() , 1 ) + open('/tmp/fftout.dat','w').write(dopack( flatten(res) , True ) ) + if doreal: + dims[-1] = int( dims[-1]/2 ) + 1 + + res = scale * res + + p.wait() + return numpy.reshape(res,dims) + +def main(): + opts,args = getopt.getopt(sys.argv[1:],'r') + opts=dict(opts) + + global doreal + doreal = opts.has_key('-r') + + if doreal: + print 'Testing multi-dimensional real FFTs' + else: + print 'Testing multi-dimensional FFTs' + + for dim in range(1,4): + test_fft( dim ) + +if __name__ == "__main__": + main() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/test/twotonetest.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,94 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include <limits.h> + + +static +double two_tone_test( int nfft, int bin1,int bin2) +{ + kiss_fftr_cfg cfg = NULL; + kiss_fft_cpx *kout = NULL; + kiss_fft_scalar *tbuf = NULL; + + int i; + double f1 = bin1*2*M_PI/nfft; + double f2 = bin2*2*M_PI/nfft; + double sigpow=0; + double noisepow=0; +#if FIXED_POINT==32 + long maxrange = LONG_MAX; +#else + long maxrange = SHRT_MAX;/* works fine for float too*/ +#endif + + cfg = kiss_fftr_alloc(nfft , 0, NULL, NULL); + tbuf = KISS_FFT_MALLOC(nfft * sizeof(kiss_fft_scalar)); + kout = KISS_FFT_MALLOC(nfft * sizeof(kiss_fft_cpx)); + + /* generate a signal with two tones*/ + for (i = 0; i < nfft; i++) { +#ifdef USE_SIMD + tbuf[i] = _mm_set1_ps( (maxrange>>1)*cos(f1*i) + + (maxrange>>1)*cos(f2*i) ); +#else + tbuf[i] = (maxrange>>1)*cos(f1*i) + + (maxrange>>1)*cos(f2*i); +#endif + } + + kiss_fftr(cfg, tbuf, kout); + + for (i=0;i < (nfft/2+1);++i) { +#ifdef USE_SIMD + double tmpr = (double)*(float*)&kout[i].r / (double)maxrange; + double tmpi = (double)*(float*)&kout[i].i / (double)maxrange; +#else + double tmpr = (double)kout[i].r / (double)maxrange; + double tmpi = (double)kout[i].i / (double)maxrange; +#endif + double mag2 = tmpr*tmpr + tmpi*tmpi; + if (i!=0 && i!= nfft/2) + mag2 *= 2; /* all bins except DC and Nyquist have symmetric counterparts implied*/ + + /* if there is power in one of the expected bins, it is signal, otherwise noise*/ + if ( i!=bin1 && i != bin2 ) + noisepow += mag2; + else + sigpow += mag2; + } + kiss_fft_cleanup(); + /*printf("TEST %d,%d,%d noise @ %fdB\n",nfft,bin1,bin2,10*log10(noisepow/sigpow +1e-30) );*/ + return 10*log10(sigpow/(noisepow+1e-50) ); +} + +int main(int argc,char ** argv) +{ + int nfft = 4*2*2*3*5; + if (argc>1) nfft = atoi(argv[1]); + + int i,j; + double minsnr = 500; + double maxsnr = -500; + double snr; + for (i=0;i<nfft/2;i+= (nfft>>4)+1) { + for (j=i;j<nfft/2;j+=(nfft>>4)+7) { + snr = two_tone_test(nfft,i,j); + if (snr<minsnr) { + minsnr=snr; + } + if (snr>maxsnr) { + maxsnr=snr; + } + } + } + snr = two_tone_test(nfft,nfft/2,nfft/2); + if (snr<minsnr) minsnr=snr; + if (snr>maxsnr) maxsnr=snr; + + printf("TwoToneTest: snr ranges from %ddB to %ddB\n",(int)minsnr,(int)maxsnr); + printf("sizeof(kiss_fft_scalar) = %d\n",(int)sizeof(kiss_fft_scalar) ); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/Makefile Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,62 @@ +WARNINGS=-W -Wall -Wstrict-prototypes -Wmissing-prototypes -Waggregate-return \ + -Wcast-align -Wcast-qual -Wnested-externs -Wshadow -Wbad-function-cast \ + -Wwrite-strings + +ifeq "$(DATATYPE)" "" + DATATYPE=float +endif + +ifeq "$(DATATYPE)" "int32_t" + TYPEFLAGS=-DFIXED_POINT=32 +endif + +ifeq "$(DATATYPE)" "int16_t" + TYPEFLAGS=-DFIXED_POINT=16 +endif + +ifeq "$(DATATYPE)" "simd" + TYPEFLAGS=-DUSE_SIMD=1 -msse +endif + +ifeq "$(TYPEFLAGS)" "" + TYPEFLAGS=-Dkiss_fft_scalar=$(DATATYPE) +endif + +ifneq ("$(KISS_FFT_USE_ALLOCA)","") + CFLAGS+= -DKISS_FFT_USE_ALLOCA=1 +endif +CFLAGS+= $(CFLAGADD) + + +FFTUTIL=fft_$(DATATYPE) +FASTFILT=fastconv_$(DATATYPE) +FASTFILTREAL=fastconvr_$(DATATYPE) +PSDPNG=psdpng_$(DATATYPE) +DUMPHDR=dumphdr_$(DATATYPE) + +all: $(FFTUTIL) $(FASTFILT) $(FASTFILTREAL) +# $(PSDPNG) +# $(DUMPHDR) + +#CFLAGS=-Wall -O3 -pedantic -march=pentiumpro -ffast-math -fomit-frame-pointer $(WARNINGS) +# If the above flags do not work, try the following +CFLAGS=-Wall -O3 $(WARNINGS) +# tip: try -openmp or -fopenmp to use multiple cores + +$(FASTFILTREAL): ../kiss_fft.c kiss_fastfir.c kiss_fftr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) -DREAL_FASTFIR $+ -DFAST_FILT_UTIL -lm + +$(FASTFILT): ../kiss_fft.c kiss_fastfir.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -DFAST_FILT_UTIL -lm + +$(FFTUTIL): ../kiss_fft.c fftutil.c kiss_fftnd.c kiss_fftr.c kiss_fftndr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm + +$(PSDPNG): ../kiss_fft.c psdpng.c kiss_fftr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lpng -lm + +$(DUMPHDR): ../kiss_fft.c dumphdr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm + +clean: + rm -f *~ fft fft_* fastconv fastconv_* fastconvr fastconvr_* psdpng psdpng_*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/fftutil.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,208 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdlib.h> +#include <math.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "kiss_fft.h" +#include "kiss_fftndr.h" + +static +void fft_file(FILE * fin,FILE * fout,int nfft,int isinverse) +{ + kiss_fft_cfg st; + kiss_fft_cpx * buf; + kiss_fft_cpx * bufout; + + buf = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * nfft ); + bufout = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * nfft ); + st = kiss_fft_alloc( nfft ,isinverse ,0,0); + + while ( fread( buf , sizeof(kiss_fft_cpx) * nfft ,1, fin ) > 0 ) { + kiss_fft( st , buf ,bufout); + fwrite( bufout , sizeof(kiss_fft_cpx) , nfft , fout ); + } + free(st); + free(buf); + free(bufout); +} + +static +void fft_filend(FILE * fin,FILE * fout,int *dims,int ndims,int isinverse) +{ + kiss_fftnd_cfg st; + kiss_fft_cpx *buf; + int dimprod=1,i; + for (i=0;i<ndims;++i) + dimprod *= dims[i]; + + buf = (kiss_fft_cpx *) malloc (sizeof (kiss_fft_cpx) * dimprod); + st = kiss_fftnd_alloc (dims, ndims, isinverse, 0, 0); + + while (fread (buf, sizeof (kiss_fft_cpx) * dimprod, 1, fin) > 0) { + kiss_fftnd (st, buf, buf); + fwrite (buf, sizeof (kiss_fft_cpx), dimprod, fout); + } + free (st); + free (buf); +} + + + +static +void fft_filend_real(FILE * fin,FILE * fout,int *dims,int ndims,int isinverse) +{ + int dimprod=1,i; + kiss_fftndr_cfg st; + void *ibuf; + void *obuf; + int insize,outsize; // size in bytes + + for (i=0;i<ndims;++i) + dimprod *= dims[i]; + insize = outsize = dimprod; + int rdim = dims[ndims-1]; + + if (isinverse) + insize = insize*2*(rdim/2+1)/rdim; + else + outsize = outsize*2*(rdim/2+1)/rdim; + + ibuf = malloc(insize*sizeof(kiss_fft_scalar)); + obuf = malloc(outsize*sizeof(kiss_fft_scalar)); + + st = kiss_fftndr_alloc(dims, ndims, isinverse, 0, 0); + + while ( fread (ibuf, sizeof(kiss_fft_scalar), insize, fin) > 0) { + if (isinverse) { + kiss_fftndri(st, + (kiss_fft_cpx*)ibuf, + (kiss_fft_scalar*)obuf); + }else{ + kiss_fftndr(st, + (kiss_fft_scalar*)ibuf, + (kiss_fft_cpx*)obuf); + } + fwrite (obuf, sizeof(kiss_fft_scalar), outsize,fout); + } + free(st); + free(ibuf); + free(obuf); +} + +static +void fft_file_real(FILE * fin,FILE * fout,int nfft,int isinverse) +{ + kiss_fftr_cfg st; + kiss_fft_scalar * rbuf; + kiss_fft_cpx * cbuf; + + rbuf = (kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar) * nfft ); + cbuf = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * (nfft/2+1) ); + st = kiss_fftr_alloc( nfft ,isinverse ,0,0); + + if (isinverse==0) { + while ( fread( rbuf , sizeof(kiss_fft_scalar) * nfft ,1, fin ) > 0 ) { + kiss_fftr( st , rbuf ,cbuf); + fwrite( cbuf , sizeof(kiss_fft_cpx) , (nfft/2 + 1) , fout ); + } + }else{ + while ( fread( cbuf , sizeof(kiss_fft_cpx) * (nfft/2+1) ,1, fin ) > 0 ) { + kiss_fftri( st , cbuf ,rbuf); + fwrite( rbuf , sizeof(kiss_fft_scalar) , nfft , fout ); + } + } + free(st); + free(rbuf); + free(cbuf); +} + +static +int get_dims(char * arg,int * dims) +{ + char *p0; + int ndims=0; + + do{ + p0 = strchr(arg,','); + if (p0) + *p0++ = '\0'; + dims[ndims++] = atoi(arg); +// fprintf(stderr,"dims[%d] = %d\n",ndims-1,dims[ndims-1]); + arg = p0; + }while (p0); + return ndims; +} + +int main(int argc,char ** argv) +{ + int isinverse=0; + int isreal=0; + FILE *fin=stdin; + FILE *fout=stdout; + int ndims=1; + int dims[32]; + dims[0] = 1024; /*default fft size*/ + + while (1) { + int c=getopt(argc,argv,"n:iR"); + if (c==-1) break; + switch (c) { + case 'n': + ndims = get_dims(optarg,dims); + break; + case 'i':isinverse=1;break; + case 'R':isreal=1;break; + case '?': + fprintf(stderr,"usage options:\n" + "\t-n d1[,d2,d3...]: fft dimension(s)\n" + "\t-i : inverse\n" + "\t-R : real input samples, not complex\n"); + exit (1); + default:fprintf(stderr,"bad %c\n",c);break; + } + } + + if ( optind < argc ) { + if (strcmp("-",argv[optind]) !=0) + fin = fopen(argv[optind],"rb"); + ++optind; + } + + if ( optind < argc ) { + if ( strcmp("-",argv[optind]) !=0 ) + fout = fopen(argv[optind],"wb"); + ++optind; + } + + if (ndims==1) { + if (isreal) + fft_file_real(fin,fout,dims[0],isinverse); + else + fft_file(fin,fout,dims[0],isinverse); + }else{ + if (isreal) + fft_filend_real(fin,fout,dims,ndims,isinverse); + else + fft_filend(fin,fout,dims,ndims,isinverse); + } + + if (fout!=stdout) fclose(fout); + if (fin!=stdin) fclose(fin); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kfc.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,116 @@ +#include "kfc.h" + +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +typedef struct cached_fft *kfc_cfg; + +struct cached_fft +{ + int nfft; + int inverse; + kiss_fft_cfg cfg; + kfc_cfg next; +}; + +static kfc_cfg cache_root=NULL; +static int ncached=0; + +static kiss_fft_cfg find_cached_fft(int nfft,int inverse) +{ + size_t len; + kfc_cfg cur=cache_root; + kfc_cfg prev=NULL; + while ( cur ) { + if ( cur->nfft == nfft && inverse == cur->inverse ) + break;/*found the right node*/ + prev = cur; + cur = prev->next; + } + if (cur== NULL) { + /* no cached node found, need to create a new one*/ + kiss_fft_alloc(nfft,inverse,0,&len); +#ifdef USE_SIMD + int padding = (16-sizeof(struct cached_fft)) & 15; + // make sure the cfg aligns on a 16 byte boundary + len += padding; +#endif + cur = (kfc_cfg)KISS_FFT_MALLOC((sizeof(struct cached_fft) + len )); + if (cur == NULL) + return NULL; + cur->cfg = (kiss_fft_cfg)(cur+1); +#ifdef USE_SIMD + cur->cfg = (kiss_fft_cfg) ((char*)(cur+1)+padding); +#endif + kiss_fft_alloc(nfft,inverse,cur->cfg,&len); + cur->nfft=nfft; + cur->inverse=inverse; + cur->next = NULL; + if ( prev ) + prev->next = cur; + else + cache_root = cur; + ++ncached; + } + return cur->cfg; +} + +void kfc_cleanup(void) +{ + kfc_cfg cur=cache_root; + kfc_cfg next=NULL; + while (cur){ + next = cur->next; + free(cur); + cur=next; + } + ncached=0; + cache_root = NULL; +} +void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) +{ + kiss_fft( find_cached_fft(nfft,0),fin,fout ); +} + +void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) +{ + kiss_fft( find_cached_fft(nfft,1),fin,fout ); +} + +#ifdef KFC_TEST +static void check(int nc) +{ + if (ncached != nc) { + fprintf(stderr,"ncached should be %d,but it is %d\n",nc,ncached); + exit(1); + } +} + +int main(void) +{ + kiss_fft_cpx buf1[1024],buf2[1024]; + memset(buf1,0,sizeof(buf1)); + check(0); + kfc_fft(512,buf1,buf2); + check(1); + kfc_fft(512,buf1,buf2); + check(1); + kfc_ifft(512,buf1,buf2); + check(2); + kfc_cleanup(); + check(0); + return 0; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kfc.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,46 @@ +#ifndef KFC_H +#define KFC_H +#include "kiss_fft.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +KFC -- Kiss FFT Cache + +Not needing to deal with kiss_fft_alloc and a config +object may be handy for a lot of programs. + +KFC uses the underlying KISS FFT functions, but caches the config object. +The first time kfc_fft or kfc_ifft for a given FFT size, the cfg +object is created for it. All subsequent calls use the cached +configuration object. + +NOTE: +You should probably not use this if your program will be using a lot +of various sizes of FFTs. There is a linear search through the +cached objects. If you are only using one or two FFT sizes, this +will be negligible. Otherwise, you may want to use another method +of managing the cfg objects. + + There is no automated cleanup of the cached objects. This could lead +to large memory usage in a program that uses a lot of *DIFFERENT* +sized FFTs. If you want to force all cached cfg objects to be freed, +call kfc_cleanup. + + */ + +/*forward complex FFT */ +void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); +/*reverse complex FFT */ +void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); + +/*free all cached objects*/ +void kfc_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fastfir.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,470 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "_kiss_fft_guts.h" + + +/* + Some definitions that allow real or complex filtering +*/ +#ifdef REAL_FASTFIR +#define MIN_FFT_LEN 2048 +#include "kiss_fftr.h" +typedef kiss_fft_scalar kffsamp_t; +typedef kiss_fftr_cfg kfcfg_t; +#define FFT_ALLOC kiss_fftr_alloc +#define FFTFWD kiss_fftr +#define FFTINV kiss_fftri +#else +#define MIN_FFT_LEN 1024 +typedef kiss_fft_cpx kffsamp_t; +typedef kiss_fft_cfg kfcfg_t; +#define FFT_ALLOC kiss_fft_alloc +#define FFTFWD kiss_fft +#define FFTINV kiss_fft +#endif + +typedef struct kiss_fastfir_state *kiss_fastfir_cfg; + + + +kiss_fastfir_cfg kiss_fastfir_alloc(const kffsamp_t * imp_resp,size_t n_imp_resp, + size_t * nfft,void * mem,size_t*lenmem); + +/* see do_file_filter for usage */ +size_t kiss_fastfir( kiss_fastfir_cfg cfg, kffsamp_t * inbuf, kffsamp_t * outbuf, size_t n, size_t *offset); + + + +static int verbose=0; + + +struct kiss_fastfir_state{ + size_t nfft; + size_t ngood; + kfcfg_t fftcfg; + kfcfg_t ifftcfg; + kiss_fft_cpx * fir_freq_resp; + kiss_fft_cpx * freqbuf; + size_t n_freq_bins; + kffsamp_t * tmpbuf; +}; + + +kiss_fastfir_cfg kiss_fastfir_alloc( + const kffsamp_t * imp_resp,size_t n_imp_resp, + size_t *pnfft, /* if <= 0, an appropriate size will be chosen */ + void * mem,size_t*lenmem) +{ + kiss_fastfir_cfg st = NULL; + size_t len_fftcfg,len_ifftcfg; + size_t memneeded = sizeof(struct kiss_fastfir_state); + char * ptr; + size_t i; + size_t nfft=0; + float scale; + int n_freq_bins; + if (pnfft) + nfft=*pnfft; + + if (nfft<=0) { + /* determine fft size as next power of two at least 2x + the impulse response length*/ + i=n_imp_resp-1; + nfft=2; + do{ + nfft<<=1; + }while (i>>=1); +#ifdef MIN_FFT_LEN + if ( nfft < MIN_FFT_LEN ) + nfft=MIN_FFT_LEN; +#endif + } + if (pnfft) + *pnfft = nfft; + +#ifdef REAL_FASTFIR + n_freq_bins = nfft/2 + 1; +#else + n_freq_bins = nfft; +#endif + /*fftcfg*/ + FFT_ALLOC (nfft, 0, NULL, &len_fftcfg); + memneeded += len_fftcfg; + /*ifftcfg*/ + FFT_ALLOC (nfft, 1, NULL, &len_ifftcfg); + memneeded += len_ifftcfg; + /* tmpbuf */ + memneeded += sizeof(kffsamp_t) * nfft; + /* fir_freq_resp */ + memneeded += sizeof(kiss_fft_cpx) * n_freq_bins; + /* freqbuf */ + memneeded += sizeof(kiss_fft_cpx) * n_freq_bins; + + if (lenmem == NULL) { + st = (kiss_fastfir_cfg) malloc (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fastfir_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->nfft = nfft; + st->ngood = nfft - n_imp_resp + 1; + st->n_freq_bins = n_freq_bins; + ptr=(char*)(st+1); + + st->fftcfg = (kfcfg_t)ptr; + ptr += len_fftcfg; + + st->ifftcfg = (kfcfg_t)ptr; + ptr += len_ifftcfg; + + st->tmpbuf = (kffsamp_t*)ptr; + ptr += sizeof(kffsamp_t) * nfft; + + st->freqbuf = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * n_freq_bins; + + st->fir_freq_resp = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * n_freq_bins; + + FFT_ALLOC (nfft,0,st->fftcfg , &len_fftcfg); + FFT_ALLOC (nfft,1,st->ifftcfg , &len_ifftcfg); + + memset(st->tmpbuf,0,sizeof(kffsamp_t)*nfft); + /*zero pad in the middle to left-rotate the impulse response + This puts the scrap samples at the end of the inverse fft'd buffer */ + st->tmpbuf[0] = imp_resp[ n_imp_resp - 1 ]; + for (i=0;i<n_imp_resp - 1; ++i) { + st->tmpbuf[ nfft - n_imp_resp + 1 + i ] = imp_resp[ i ]; + } + + FFTFWD(st->fftcfg,st->tmpbuf,st->fir_freq_resp); + + /* TODO: this won't work for fixed point */ + scale = 1.0 / st->nfft; + + for ( i=0; i < st->n_freq_bins; ++i ) { +#ifdef USE_SIMD + st->fir_freq_resp[i].r *= _mm_set1_ps(scale); + st->fir_freq_resp[i].i *= _mm_set1_ps(scale); +#else + st->fir_freq_resp[i].r *= scale; + st->fir_freq_resp[i].i *= scale; +#endif + } + return st; +} + +static void fastconv1buf(const kiss_fastfir_cfg st,const kffsamp_t * in,kffsamp_t * out) +{ + size_t i; + /* multiply the frequency response of the input signal by + that of the fir filter*/ + FFTFWD( st->fftcfg, in , st->freqbuf ); + for ( i=0; i<st->n_freq_bins; ++i ) { + kiss_fft_cpx tmpsamp; + C_MUL(tmpsamp,st->freqbuf[i],st->fir_freq_resp[i]); + st->freqbuf[i] = tmpsamp; + } + + /* perform the inverse fft*/ + FFTINV(st->ifftcfg,st->freqbuf,out); +} + +/* n : the size of inbuf and outbuf in samples + return value: the number of samples completely processed + n-retval samples should be copied to the front of the next input buffer */ +static size_t kff_nocopy( + kiss_fastfir_cfg st, + const kffsamp_t * inbuf, + kffsamp_t * outbuf, + size_t n) +{ + size_t norig=n; + while (n >= st->nfft ) { + fastconv1buf(st,inbuf,outbuf); + inbuf += st->ngood; + outbuf += st->ngood; + n -= st->ngood; + } + return norig - n; +} + +static +size_t kff_flush(kiss_fastfir_cfg st,const kffsamp_t * inbuf,kffsamp_t * outbuf,size_t n) +{ + size_t zpad=0,ntmp; + + ntmp = kff_nocopy(st,inbuf,outbuf,n); + n -= ntmp; + inbuf += ntmp; + outbuf += ntmp; + + zpad = st->nfft - n; + memset(st->tmpbuf,0,sizeof(kffsamp_t)*st->nfft ); + memcpy(st->tmpbuf,inbuf,sizeof(kffsamp_t)*n ); + + fastconv1buf(st,st->tmpbuf,st->tmpbuf); + + memcpy(outbuf,st->tmpbuf,sizeof(kffsamp_t)*( st->ngood - zpad )); + return ntmp + st->ngood - zpad; +} + +size_t kiss_fastfir( + kiss_fastfir_cfg vst, + kffsamp_t * inbuf, + kffsamp_t * outbuf, + size_t n_new, + size_t *offset) +{ + size_t ntot = n_new + *offset; + if (n_new==0) { + return kff_flush(vst,inbuf,outbuf,ntot); + }else{ + size_t nwritten = kff_nocopy(vst,inbuf,outbuf,ntot); + *offset = ntot - nwritten; + /*save the unused or underused samples at the front of the input buffer */ + memcpy( inbuf , inbuf+nwritten , *offset * sizeof(kffsamp_t) ); + return nwritten; + } +} + +#ifdef FAST_FILT_UTIL +#include <unistd.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <assert.h> + +static +void direct_file_filter( + FILE * fin, + FILE * fout, + const kffsamp_t * imp_resp, + size_t n_imp_resp) +{ + size_t nlag = n_imp_resp - 1; + + const kffsamp_t *tmph; + kffsamp_t *buf, *circbuf; + kffsamp_t outval; + size_t nread; + size_t nbuf; + size_t oldestlag = 0; + size_t k, tap; +#ifndef REAL_FASTFIR + kffsamp_t tmp; +#endif + + nbuf = 4096; + buf = (kffsamp_t *) malloc ( sizeof (kffsamp_t) * nbuf); + circbuf = (kffsamp_t *) malloc (sizeof (kffsamp_t) * nlag); + if (!circbuf || !buf) { + perror("circbuf allocation"); + exit(1); + } + + if ( fread (circbuf, sizeof (kffsamp_t), nlag, fin) != nlag ) { + perror ("insufficient data to overcome transient"); + exit (1); + } + + do { + nread = fread (buf, sizeof (kffsamp_t), nbuf, fin); + if (nread <= 0) + break; + + for (k = 0; k < nread; ++k) { + tmph = imp_resp+nlag; +#ifdef REAL_FASTFIR +# ifdef USE_SIMD + outval = _mm_set1_ps(0); +#else + outval = 0; +#endif + for (tap = oldestlag; tap < nlag; ++tap) + outval += circbuf[tap] * *tmph--; + for (tap = 0; tap < oldestlag; ++tap) + outval += circbuf[tap] * *tmph--; + outval += buf[k] * *tmph; +#else +# ifdef USE_SIMD + outval.r = outval.i = _mm_set1_ps(0); +#else + outval.r = outval.i = 0; +#endif + for (tap = oldestlag; tap < nlag; ++tap){ + C_MUL(tmp,circbuf[tap],*tmph); + --tmph; + C_ADDTO(outval,tmp); + } + + for (tap = 0; tap < oldestlag; ++tap) { + C_MUL(tmp,circbuf[tap],*tmph); + --tmph; + C_ADDTO(outval,tmp); + } + C_MUL(tmp,buf[k],*tmph); + C_ADDTO(outval,tmp); +#endif + + circbuf[oldestlag++] = buf[k]; + buf[k] = outval; + + if (oldestlag == nlag) + oldestlag = 0; + } + + if (fwrite (buf, sizeof (buf[0]), nread, fout) != nread) { + perror ("short write"); + exit (1); + } + } while (nread); + free (buf); + free (circbuf); +} + +static +void do_file_filter( + FILE * fin, + FILE * fout, + const kffsamp_t * imp_resp, + size_t n_imp_resp, + size_t nfft ) +{ + int fdout; + size_t n_samps_buf; + + kiss_fastfir_cfg cfg; + kffsamp_t *inbuf,*outbuf; + int nread,nwrite; + size_t idx_inbuf; + + fdout = fileno(fout); + + cfg=kiss_fastfir_alloc(imp_resp,n_imp_resp,&nfft,0,0); + + /* use length to minimize buffer shift*/ + n_samps_buf = 8*4096/sizeof(kffsamp_t); + n_samps_buf = nfft + 4*(nfft-n_imp_resp+1); + + if (verbose) fprintf(stderr,"bufsize=%d\n",(int)(sizeof(kffsamp_t)*n_samps_buf) ); + + + /*allocate space and initialize pointers */ + inbuf = (kffsamp_t*)malloc(sizeof(kffsamp_t)*n_samps_buf); + outbuf = (kffsamp_t*)malloc(sizeof(kffsamp_t)*n_samps_buf); + + idx_inbuf=0; + do{ + /* start reading at inbuf[idx_inbuf] */ + nread = fread( inbuf + idx_inbuf, sizeof(kffsamp_t), n_samps_buf - idx_inbuf,fin ); + + /* If nread==0, then this is a flush. + The total number of samples in input is idx_inbuf + nread . */ + nwrite = kiss_fastfir(cfg, inbuf, outbuf,nread,&idx_inbuf) * sizeof(kffsamp_t); + /* kiss_fastfir moved any unused samples to the front of inbuf and updated idx_inbuf */ + + if ( write(fdout, outbuf, nwrite) != nwrite ) { + perror("short write"); + exit(1); + } + }while ( nread ); + free(cfg); + free(inbuf); + free(outbuf); +} + +int main(int argc,char**argv) +{ + kffsamp_t * h; + int use_direct=0; + size_t nh,nfft=0; + FILE *fin=stdin; + FILE *fout=stdout; + FILE *filtfile=NULL; + while (1) { + int c=getopt(argc,argv,"n:h:i:o:vd"); + if (c==-1) break; + switch (c) { + case 'v': + verbose=1; + break; + case 'n': + nfft=atoi(optarg); + break; + case 'i': + fin = fopen(optarg,"rb"); + if (fin==NULL) { + perror(optarg); + exit(1); + } + break; + case 'o': + fout = fopen(optarg,"w+b"); + if (fout==NULL) { + perror(optarg); + exit(1); + } + break; + case 'h': + filtfile = fopen(optarg,"rb"); + if (filtfile==NULL) { + perror(optarg); + exit(1); + } + break; + case 'd': + use_direct=1; + break; + case '?': + fprintf(stderr,"usage options:\n" + "\t-n nfft: fft size to use\n" + "\t-d : use direct FIR filtering, not fast convolution\n" + "\t-i filename: input file\n" + "\t-o filename: output(filtered) file\n" + "\t-n nfft: fft size to use\n" + "\t-h filename: impulse response\n"); + exit (1); + default:fprintf(stderr,"bad %c\n",c);break; + } + } + if (filtfile==NULL) { + fprintf(stderr,"You must supply the FIR coeffs via -h\n"); + exit(1); + } + fseek(filtfile,0,SEEK_END); + nh = ftell(filtfile) / sizeof(kffsamp_t); + if (verbose) fprintf(stderr,"%d samples in FIR filter\n",(int)nh); + h = (kffsamp_t*)malloc(sizeof(kffsamp_t)*nh); + fseek(filtfile,0,SEEK_SET); + if (fread(h,sizeof(kffsamp_t),nh,filtfile) != nh) + fprintf(stderr,"short read on filter file\n"); + + fclose(filtfile); + + if (use_direct) + direct_file_filter( fin, fout, h,nh); + else + do_file_filter( fin, fout, h,nh,nfft); + + if (fout!=stdout) fclose(fout); + if (fin!=stdin) fclose(fin); + + return 0; +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftnd.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,193 @@ + + +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kiss_fftnd.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftnd_state{ + int dimprod; /* dimsum would be mighty tasty right now */ + int ndims; + int *dims; + kiss_fft_cfg *states; /* cfg states for each dimension */ + kiss_fft_cpx * tmpbuf; /*buffer capable of hold the entire input */ +}; + +kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) +{ + kiss_fftnd_cfg st = NULL; + int i; + int dimprod=1; + size_t memneeded = sizeof(struct kiss_fftnd_state); + char * ptr; + + for (i=0;i<ndims;++i) { + size_t sublen=0; + kiss_fft_alloc (dims[i], inverse_fft, NULL, &sublen); + memneeded += sublen; /* st->states[i] */ + dimprod *= dims[i]; + } + memneeded += sizeof(int) * ndims;/* st->dims */ + memneeded += sizeof(void*) * ndims;/* st->states */ + memneeded += sizeof(kiss_fft_cpx) * dimprod; /* st->tmpbuf */ + + if (lenmem == NULL) {/* allocate for the caller*/ + st = (kiss_fftnd_cfg) malloc (memneeded); + } else { /* initialize supplied buffer if big enough */ + if (*lenmem >= memneeded) + st = (kiss_fftnd_cfg) mem; + *lenmem = memneeded; /*tell caller how big struct is (or would be) */ + } + if (!st) + return NULL; /*malloc failed or buffer too small */ + + st->dimprod = dimprod; + st->ndims = ndims; + ptr=(char*)(st+1); + + st->states = (kiss_fft_cfg *)ptr; + ptr += sizeof(void*) * ndims; + + st->dims = (int*)ptr; + ptr += sizeof(int) * ndims; + + st->tmpbuf = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * dimprod; + + for (i=0;i<ndims;++i) { + size_t len; + st->dims[i] = dims[i]; + kiss_fft_alloc (st->dims[i], inverse_fft, NULL, &len); + st->states[i] = kiss_fft_alloc (st->dims[i], inverse_fft, ptr,&len); + ptr += len; + } + /* +Hi there! + +If you're looking at this particular code, it probably means you've got a brain-dead bounds checker +that thinks the above code overwrites the end of the array. + +It doesn't. + +-- Mark + +P.S. +The below code might give you some warm fuzzies and help convince you. + */ + if ( ptr - (char*)st != (int)memneeded ) { + fprintf(stderr, + "################################################################################\n" + "Internal error! Memory allocation miscalculation\n" + "################################################################################\n" + ); + } + return st; +} + +/* + This works by tackling one dimension at a time. + + In effect, + Each stage starts out by reshaping the matrix into a DixSi 2d matrix. + A Di-sized fft is taken of each column, transposing the matrix as it goes. + +Here's a 3-d example: +Take a 2x3x4 matrix, laid out in memory as a contiguous buffer + [ [ [ a b c d ] [ e f g h ] [ i j k l ] ] + [ [ m n o p ] [ q r s t ] [ u v w x ] ] ] + +Stage 0 ( D=2): treat the buffer as a 2x12 matrix + [ [a b ... k l] + [m n ... w x] ] + + FFT each column with size 2. + Transpose the matrix at the same time using kiss_fft_stride. + + [ [ a+m a-m ] + [ b+n b-n] + ... + [ k+w k-w ] + [ l+x l-x ] ] + + Note fft([x y]) == [x+y x-y] + +Stage 1 ( D=3) treats the buffer (the output of stage D=2) as an 3x8 matrix, + [ [ a+m a-m b+n b-n c+o c-o d+p d-p ] + [ e+q e-q f+r f-r g+s g-s h+t h-t ] + [ i+u i-u j+v j-v k+w k-w l+x l-x ] ] + + And perform FFTs (size=3) on each of the columns as above, transposing + the matrix as it goes. The output of stage 1 is + (Legend: ap = [ a+m e+q i+u ] + am = [ a-m e-q i-u ] ) + + [ [ sum(ap) fft(ap)[0] fft(ap)[1] ] + [ sum(am) fft(am)[0] fft(am)[1] ] + [ sum(bp) fft(bp)[0] fft(bp)[1] ] + [ sum(bm) fft(bm)[0] fft(bm)[1] ] + [ sum(cp) fft(cp)[0] fft(cp)[1] ] + [ sum(cm) fft(cm)[0] fft(cm)[1] ] + [ sum(dp) fft(dp)[0] fft(dp)[1] ] + [ sum(dm) fft(dm)[0] fft(dm)[1] ] ] + +Stage 2 ( D=4) treats this buffer as a 4*6 matrix, + [ [ sum(ap) fft(ap)[0] fft(ap)[1] sum(am) fft(am)[0] fft(am)[1] ] + [ sum(bp) fft(bp)[0] fft(bp)[1] sum(bm) fft(bm)[0] fft(bm)[1] ] + [ sum(cp) fft(cp)[0] fft(cp)[1] sum(cm) fft(cm)[0] fft(cm)[1] ] + [ sum(dp) fft(dp)[0] fft(dp)[1] sum(dm) fft(dm)[0] fft(dm)[1] ] ] + + Then FFTs each column, transposing as it goes. + + The resulting matrix is the 3d FFT of the 2x3x4 input matrix. + + Note as a sanity check that the first element of the final + stage's output (DC term) is + sum( [ sum(ap) sum(bp) sum(cp) sum(dp) ] ) + , i.e. the summation of all 24 input elements. + +*/ +void kiss_fftnd(kiss_fftnd_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + int i,k; + const kiss_fft_cpx * bufin=fin; + kiss_fft_cpx * bufout; + + /*arrange it so the last bufout == fout*/ + if ( st->ndims & 1 ) { + bufout = fout; + if (fin==fout) { + memcpy( st->tmpbuf, fin, sizeof(kiss_fft_cpx) * st->dimprod ); + bufin = st->tmpbuf; + } + }else + bufout = st->tmpbuf; + + for ( k=0; k < st->ndims; ++k) { + int curdim = st->dims[k]; + int stride = st->dimprod / curdim; + + for ( i=0 ; i<stride ; ++i ) + kiss_fft_stride( st->states[k], bufin+i , bufout+i*curdim, stride ); + + /*toggle back and forth between the two buffers*/ + if (bufout == st->tmpbuf){ + bufout = fout; + bufin = st->tmpbuf; + }else{ + bufout = st->tmpbuf; + bufin = fout; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftnd.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,18 @@ +#ifndef KISS_FFTND_H +#define KISS_FFTND_H + +#include "kiss_fft.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct kiss_fftnd_state * kiss_fftnd_cfg; + +kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); +void kiss_fftnd(kiss_fftnd_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +#ifdef __cplusplus +} +#endif +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftndr.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,118 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kiss_fftndr.h" +#include "_kiss_fft_guts.h" +#define MAX(x,y) ( ( (x)<(y) )?(y):(x) ) + +struct kiss_fftndr_state +{ + int dimReal; + int dimOther; + kiss_fftr_cfg cfg_r; + kiss_fftnd_cfg cfg_nd; + void * tmpbuf; +}; + +static int prod(const int *dims, int ndims) +{ + int x=1; + while (ndims--) + x *= *dims++; + return x; +} + +kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) +{ + kiss_fftndr_cfg st = NULL; + size_t nr=0 , nd=0,ntmp=0; + int dimReal = dims[ndims-1]; + int dimOther = prod(dims,ndims-1); + size_t memneeded; + + (void)kiss_fftr_alloc(dimReal,inverse_fft,NULL,&nr); + (void)kiss_fftnd_alloc(dims,ndims-1,inverse_fft,NULL,&nd); + ntmp = + MAX( 2*dimOther , dimReal+2) * sizeof(kiss_fft_scalar) // freq buffer for one pass + + dimOther*(dimReal+2) * sizeof(kiss_fft_scalar); // large enough to hold entire input in case of in-place + + memneeded = sizeof( struct kiss_fftndr_state ) + nr + nd + ntmp; + + if (lenmem==NULL) { + st = (kiss_fftndr_cfg) malloc(memneeded); + }else{ + if (*lenmem >= memneeded) + st = (kiss_fftndr_cfg)mem; + *lenmem = memneeded; + } + if (st==NULL) + return NULL; + memset( st , 0 , memneeded); + + st->dimReal = dimReal; + st->dimOther = dimOther; + st->cfg_r = kiss_fftr_alloc( dimReal,inverse_fft,st+1,&nr); + st->cfg_nd = kiss_fftnd_alloc(dims,ndims-1,inverse_fft, ((char*) st->cfg_r)+nr,&nd); + st->tmpbuf = (char*)st->cfg_nd + nd; + + return st; +} + +void kiss_fftndr(kiss_fftndr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + int k1,k2; + int dimReal = st->dimReal; + int dimOther = st->dimOther; + int nrbins = dimReal/2+1; + + kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; + kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); + + // timedata is N0 x N1 x ... x Nk real + + // take a real chunk of data, fft it and place the output at correct intervals + for (k1=0;k1<dimOther;++k1) { + kiss_fftr( st->cfg_r, timedata + k1*dimReal , tmp1 ); // tmp1 now holds nrbins complex points + for (k2=0;k2<nrbins;++k2) + tmp2[ k2*dimOther+k1 ] = tmp1[k2]; + } + + for (k2=0;k2<nrbins;++k2) { + kiss_fftnd(st->cfg_nd, tmp2+k2*dimOther, tmp1); // tmp1 now holds dimOther complex points + for (k1=0;k1<dimOther;++k1) + freqdata[ k1*(nrbins) + k2] = tmp1[k1]; + } +} + +void kiss_fftndri(kiss_fftndr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) +{ + int k1,k2; + int dimReal = st->dimReal; + int dimOther = st->dimOther; + int nrbins = dimReal/2+1; + kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; + kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); + + for (k2=0;k2<nrbins;++k2) { + for (k1=0;k1<dimOther;++k1) + tmp1[k1] = freqdata[ k1*(nrbins) + k2 ]; + kiss_fftnd(st->cfg_nd, tmp1, tmp2+k2*dimOther); + } + + for (k1=0;k1<dimOther;++k1) { + for (k2=0;k2<nrbins;++k2) + tmp1[k2] = tmp2[ k2*dimOther+k1 ]; + kiss_fftri( st->cfg_r,tmp1,timedata + k1*dimReal); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftndr.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,47 @@ +#ifndef KISS_NDR_H +#define KISS_NDR_H + +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include "kiss_fftnd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct kiss_fftndr_state *kiss_fftndr_cfg; + + +kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); +/* + dims[0] must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftndr( + kiss_fftndr_cfg cfg, + const kiss_fft_scalar *timedata, + kiss_fft_cpx *freqdata); +/* + input timedata has dims[0] X dims[1] X ... X dims[ndims-1] scalar points + output freqdata has dims[0] X dims[1] X ... X dims[ndims-1]/2+1 complex points +*/ + +void kiss_fftndri( + kiss_fftndr_cfg cfg, + const kiss_fft_cpx *freqdata, + kiss_fft_scalar *timedata); +/* + input and output dimensions are the exact opposite of kiss_fftndr +*/ + + +#define kiss_fftr_free free + +#ifdef __cplusplus +} +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftr.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,159 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + void * pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize, memneeded; + + if (nfft & 1) { + fprintf(stderr,"Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + + for (i = 0; i < nfft/2; ++i) { + double phase = + -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); + if (inverse_fft) + phase *= -1; + kf_cexp (st->super_twiddles+i,phase); + } + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + fprintf(stderr,"kiss fft usage error: improper alloc\n"); + exit(1); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k-1]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + fprintf (stderr, "kiss fft usage error: improper alloc\n"); + exit (1); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + C_FIXDIV(st->tmpbuf[0],2); + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 ); + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k-1]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/kiss_fftr.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,46 @@ +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free free + +#ifdef __cplusplus +} +#endif +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/src/ext/kissfft/tools/psdpng.c Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,235 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdlib.h> +#include <math.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <png.h> + +#include "kiss_fft.h" +#include "kiss_fftr.h" + +int nfft=1024; +FILE * fin=NULL; +FILE * fout=NULL; + +int navg=20; +int remove_dc=0; +int nrows=0; +float * vals=NULL; +int stereo=0; + +static +void config(int argc,char** argv) +{ + while (1) { + int c = getopt (argc, argv, "n:r:as"); + if (c == -1) + break; + switch (c) { + case 'n': nfft=(int)atoi(optarg);break; + case 'r': navg=(int)atoi(optarg);break; + case 'a': remove_dc=1;break; + case 's': stereo=1;break; + case '?': + fprintf (stderr, "usage options:\n" + "\t-n d: fft dimension(s) [1024]\n" + "\t-r d: number of rows to average [20]\n" + "\t-a : remove average from each fft buffer\n" + "\t-s : input is stereo, channels will be combined before fft\n" + "16 bit machine format real input is assumed\n" + ); + default: + fprintf (stderr, "bad %c\n", c); + exit (1); + break; + } + } + if ( optind < argc ) { + if (strcmp("-",argv[optind]) !=0) + fin = fopen(argv[optind],"rb"); + ++optind; + } + + if ( optind < argc ) { + if ( strcmp("-",argv[optind]) !=0 ) + fout = fopen(argv[optind],"wb"); + ++optind; + } + if (fin==NULL) + fin=stdin; + if (fout==NULL) + fout=stdout; +} + +#define CHECKNULL(p) if ( (p)==NULL ) do { fprintf(stderr,"CHECKNULL failed @ %s(%d): %s\n",__FILE__,__LINE__,#p );exit(1);} while(0) + +typedef struct +{ + png_byte r; + png_byte g; + png_byte b; +} rgb_t; + +static +void val2rgb(float x,rgb_t *p) +{ + const double pi = 3.14159265358979; + p->g = (int)(255*sin(x*pi)); + p->r = (int)(255*abs(sin(x*pi*3/2))); + p->b = (int)(255*abs(sin(x*pi*5/2))); + //fprintf(stderr,"%.2f : %d,%d,%d\n",x,(int)p->r,(int)p->g,(int)p->b); +} + +static +void cpx2pixels(rgb_t * res,const float * fbuf,size_t n) +{ + size_t i; + float minval,maxval,valrange; + minval=maxval=fbuf[0]; + + for (i = 0; i < n; ++i) { + if (fbuf[i] > maxval) maxval = fbuf[i]; + if (fbuf[i] < minval) minval = fbuf[i]; + } + + fprintf(stderr,"min ==%f,max=%f\n",minval,maxval); + valrange = maxval-minval; + if (valrange == 0) { + fprintf(stderr,"min == max == %f\n",minval); + exit (1); + } + + for (i = 0; i < n; ++i) + val2rgb( (fbuf[i] - minval)/valrange , res+i ); +} + +static +void transform_signal(void) +{ + short *inbuf; + kiss_fftr_cfg cfg=NULL; + kiss_fft_scalar *tbuf; + kiss_fft_cpx *fbuf; + float *mag2buf; + int i; + int n; + int avgctr=0; + + int nfreqs=nfft/2+1; + + CHECKNULL( cfg=kiss_fftr_alloc(nfft,0,0,0) ); + CHECKNULL( inbuf=(short*)malloc(sizeof(short)*2*nfft ) ); + CHECKNULL( tbuf=(kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar)*nfft ) ); + CHECKNULL( fbuf=(kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*nfreqs ) ); + CHECKNULL( mag2buf=(float*)malloc(sizeof(float)*nfreqs ) ); + + memset(mag2buf,0,sizeof(mag2buf)*nfreqs); + + while (1) { + if (stereo) { + n = fread(inbuf,sizeof(short)*2,nfft,fin); + if (n != nfft ) + break; + for (i=0;i<nfft;++i) + tbuf[i] = inbuf[2*i] + inbuf[2*i+1]; + }else{ + n = fread(inbuf,sizeof(short),nfft,fin); + if (n != nfft ) + break; + for (i=0;i<nfft;++i) + tbuf[i] = inbuf[i]; + } + + if (remove_dc) { + float avg = 0; + for (i=0;i<nfft;++i) avg += tbuf[i]; + avg /= nfft; + for (i=0;i<nfft;++i) tbuf[i] -= (kiss_fft_scalar)avg; + } + + /* do FFT */ + kiss_fftr(cfg,tbuf,fbuf); + + for (i=0;i<nfreqs;++i) + mag2buf[i] += fbuf[i].r * fbuf[i].r + fbuf[i].i * fbuf[i].i; + + if (++avgctr == navg) { + avgctr=0; + ++nrows; + vals = (float*)realloc(vals,sizeof(float)*nrows*nfreqs); + float eps = 1; + for (i=0;i<nfreqs;++i) + vals[(nrows - 1) * nfreqs + i] = 10 * log10 ( mag2buf[i] / navg + eps ); + memset(mag2buf,0,sizeof(mag2buf[0])*nfreqs); + } + } + + free(cfg); + free(inbuf); + free(tbuf); + free(fbuf); + free(mag2buf); +} + +static +void make_png(void) +{ + png_bytepp row_pointers=NULL; + rgb_t * row_data=NULL; + int i; + int nfreqs = nfft/2+1; + + png_structp png_ptr=NULL; + png_infop info_ptr=NULL; + + CHECKNULL( png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,0,0,0) ); + CHECKNULL( info_ptr = png_create_info_struct(png_ptr) ); + + + png_init_io(png_ptr, fout ); + png_set_IHDR(png_ptr, info_ptr ,nfreqs,nrows,8,PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT ); + + + row_data = (rgb_t*)malloc(sizeof(rgb_t) * nrows * nfreqs) ; + cpx2pixels(row_data, vals, nfreqs*nrows ); + + row_pointers = realloc(row_pointers, nrows*sizeof(png_bytep)); + for (i=0;i<nrows;++i) { + row_pointers[i] = (png_bytep)(row_data + i*nfreqs); + } + png_set_rows(png_ptr, info_ptr, row_pointers); + + + fprintf(stderr,"creating %dx%d png\n",nfreqs,nrows); + fprintf(stderr,"bitdepth %d \n",png_get_bit_depth(png_ptr,info_ptr ) ); + + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY , NULL); + +} + +int main(int argc,char ** argv) +{ + config(argc,argv); + + transform_signal(); + + make_png(); + + if (fout!=stdout) fclose(fout); + if (fin!=stdin) fclose(fin); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestCQFrequency.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,132 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "cq/CQSpectrogram.h" + +#include "dsp/Window.h" + +#include <cmath> +#include <vector> +#include <iostream> + +using std::vector; +using std::cerr; +using std::endl; + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestCQFrequency) + +// The principle here is to feed a single windowed sinusoid into a +// small CQ transform and check that the output has its peak bin at +// the correct frequency. + +// Set up fs/2 = 50, frequency range 10 -> 40 i.e. 2 octaves, fixed +// duration of 2 seconds +static const double sampleRate = 100; +static const double cqmin = 11.8921; +static const double cqmax = 40; +static const double bpo = 4; +static const int duration = sampleRate * 2; + +// Threshold below which to ignore a column completely +static const double threshold = 0.08; + +int +binForFrequency(double freq) +{ + int bin = (bpo * 2) - round(bpo * log2(freq / cqmin)) - 1; + return bin; +} + +void +checkCQFreqColumn(int i, vector<double> column, + double freq, CQSpectrogram::Interpolation interp) +{ + double maxval = 0.0; + int maxidx = -1; + int height = column.size(); + + int nonZeroHeight = ((i % 2 == 1) ? height/2 : height); + + for (int j = 0; j < nonZeroHeight; ++j) { + if (j == 0 || column[j] > maxval) { + maxval = column[j]; + maxidx = j; + } + } + + int expected = binForFrequency(freq); + if (maxval < threshold) { + return; // ignore these columns at start and end + } else if (expected < nonZeroHeight && maxidx != expected) { + cerr << "ERROR: In column " << i << " with interpolation " << interp + << ", maximum value for frequency " << freq + << "\n found at index " << maxidx + << " (expected index " << expected << ")" << endl; + cerr << "column contains: "; + for (int j = 0; j < height; ++j) { + cerr << column[j] << " "; + } + cerr << endl; + BOOST_CHECK_EQUAL(maxidx, expected); + } +} + +void +testCQFrequencyWith(CQParameters params, + CQSpectrogram::Interpolation interp, + double freq) +{ + CQSpectrogram cq(params, interp); + + BOOST_CHECK_EQUAL(cq.getBinsPerOctave(), bpo); + BOOST_CHECK_EQUAL(cq.getOctaves(), 2); + BOOST_CHECK_CLOSE(cq.getBinFrequency(0), 40, 1e-10); + BOOST_CHECK_CLOSE(cq.getBinFrequency(4), 20, 1e-10); + BOOST_CHECK_CLOSE(cq.getBinFrequency(7), cqmin, 1e-3); + + vector<double> input; + for (int i = 0; i < duration; ++i) { + input.push_back(sin((i * 2 * M_PI * freq) / sampleRate)); + } + Window<double>(HanningWindow, duration).cut(input.data()); + + CQSpectrogram::RealBlock output = cq.process(input); + CQSpectrogram::RealBlock rest = cq.getRemainingOutput(); + output.insert(output.end(), rest.begin(), rest.end()); + + BOOST_CHECK_EQUAL(output[0].size(), + cq.getBinsPerOctave() * cq.getOctaves()); + + for (int i = 0; i < int(output.size()); ++i) { + checkCQFreqColumn(i, output[i], freq, interp); + } +} + +void +testCQFrequency(double freq) +{ + vector<CQSpectrogram::Interpolation> interpolationTypes; + interpolationTypes.push_back(CQSpectrogram::InterpolateZeros); + interpolationTypes.push_back(CQSpectrogram::InterpolateHold); + interpolationTypes.push_back(CQSpectrogram::InterpolateLinear); + + for (int k = 0; k < int(interpolationTypes.size()); ++k) { + CQSpectrogram::Interpolation interp = interpolationTypes[k]; + CQParameters params(sampleRate, cqmin, cqmax, bpo); + testCQFrequencyWith(params, interp, freq); + } +} + +BOOST_AUTO_TEST_CASE(freq_11) { testCQFrequency(11); } +BOOST_AUTO_TEST_CASE(freq_17) { testCQFrequency(17); } +BOOST_AUTO_TEST_CASE(freq_24) { testCQFrequency(24); } +BOOST_AUTO_TEST_CASE(freq_27) { testCQFrequency(27); } +BOOST_AUTO_TEST_CASE(freq_33) { testCQFrequency(33); } +BOOST_AUTO_TEST_CASE(freq_40) { testCQFrequency(40); } + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestCQKernel.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "cq/CQKernel.h" + +#include <cmath> +#include <vector> +#include <iostream> + +using std::vector; +using std::cerr; +using std::endl; + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +static int rate = 123; +static int max = 60; +static int min = 12; +static int bpo = 4; + +BOOST_AUTO_TEST_SUITE(TestCQKernel) + +// Just some simple tests on kernel construction -- make sure it's the +// right size, etc + +BOOST_AUTO_TEST_CASE(sampleRate) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_EQUAL(k.getProperties().sampleRate, rate); +} + +BOOST_AUTO_TEST_CASE(binsPerOctave) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_EQUAL(k.getProperties().binsPerOctave, bpo); +} + +BOOST_AUTO_TEST_CASE(maxFrequency) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_EQUAL(k.getProperties().maxFrequency, max); +} + +BOOST_AUTO_TEST_CASE(minFrequency) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_CLOSE(k.getProperties().minFrequency, + (max / 2.0) * pow(2, 1.0/bpo), + 1e-8); +} + +BOOST_AUTO_TEST_CASE(atomsPerFrame) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_EQUAL(k.getProperties().atomsPerFrame, 5); +} + +BOOST_AUTO_TEST_CASE(fftSize) { + CQParameters params(rate, min, max, bpo); + CQKernel k(params); + BOOST_CHECK_EQUAL(k.getProperties().fftSize, 32); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestCQTime.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,120 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "cq/CQSpectrogram.h" + +#include "dsp/Window.h" + +#include <cmath> +#include <vector> +#include <iostream> + +using std::vector; +using std::cerr; +using std::endl; + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestCQTime) + +// Principle: Run a Dirac impulse through the CQ transform and check +// that its output has all the peak bins aligned correctly in time. + +// Set up fs/2 = 50, frequency range 10 -> 40 i.e. 2 octaves, fixed +// duration of 2 seconds +static const double sampleRate = 100; +static const double cqmin = 10; +static const double cqmax = 40; +static const double bpo = 4; +static const int duration = sampleRate * 2; + +// Threshold below which to ignore a column completely +static const double threshold = 0.08; + +void +testCQTime(double t) +{ + vector<CQSpectrogram::Interpolation> interpolationTypes; + interpolationTypes.push_back(CQSpectrogram::InterpolateZeros); + interpolationTypes.push_back(CQSpectrogram::InterpolateHold); + interpolationTypes.push_back(CQSpectrogram::InterpolateLinear); + + for (int k = 0; k < int(interpolationTypes.size()); ++k) { + + CQSpectrogram::Interpolation interp = interpolationTypes[k]; + + CQParameters params(sampleRate, cqmin, cqmax, bpo); + CQSpectrogram cq(params, interp); + + BOOST_CHECK_EQUAL(cq.getBinsPerOctave(), bpo); + BOOST_CHECK_EQUAL(cq.getOctaves(), 2); + + vector<double> input(duration, 0.0); + int ix = int(floor(t * sampleRate)); + if (ix >= duration) ix = duration-1; + input[ix] = 1.0; + + CQSpectrogram::RealBlock output = cq.process(input); + CQSpectrogram::RealBlock rest = cq.getRemainingOutput(); + output.insert(output.end(), rest.begin(), rest.end()); + + BOOST_CHECK_EQUAL(output[0].size(), + cq.getBinsPerOctave() * cq.getOctaves()); + + vector<int> peaks; + double eps = 1e-8; + + for (int j = 0; j < int(output[0].size()); ++j) { + + int maxidx = -1; + double max = 0.0; + for (int i = 0; i < int(output.size()); ++i) { + double value = output[i][j]; + if (i == 0 || value + eps > max) { + max = value; + maxidx = i; + } + } + + peaks.push_back(maxidx); + } + + for (int j = 1; j < int(peaks.size()); ++j) { + int oct = j / bpo; + int spacing = (1 << oct); + int actual = peaks[j]/spacing; + int expected = int(round(double(peaks[0])/spacing)); + if (actual != expected) { + cerr << "ERROR: In row " << j << " (bin freq " + << cq.getBinFrequency(j) << "), interpolation " << interp + << ", maximum value for time " << t + << "\n found at index " << peaks[j] + << " of " << output.size() << " which does not align with" + << " highest frequency\n bin peak at " << peaks[0] + << " given octave spacing of " << spacing + << "\n [latency = " << cq.getLatency() + << ", hop = " << cq.getColumnHop() << ", duration = " + << duration << ", ix = " << ix << "]" << endl; + cerr << "row contains: "; + for (int i = 0; i < int(output.size()); ++i) { + if (i == expected * spacing) cerr << "*"; + if (i == peaks[j]) cerr << "**"; + cerr << output[i][j] << " "; + } + cerr << endl; + + BOOST_CHECK_EQUAL(actual, expected); + } + } + } +} + +BOOST_AUTO_TEST_CASE(time_zero) { testCQTime(0); } +BOOST_AUTO_TEST_CASE(time_half) { testCQTime(0.5); } +BOOST_AUTO_TEST_CASE(time_one) { testCQTime(1.0); } +BOOST_AUTO_TEST_CASE(time_two) { testCQTime(2.0); } + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestFFT.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,466 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "dsp/FFT.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +#include <stdexcept> + +BOOST_AUTO_TEST_SUITE(TestFFT) + +#define COMPARE_CONST(a, n) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - n, 1e-14); \ + } + +#define COMPARE_ARRAY(a, b) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +//!!! need at least one test with complex time-domain signal + +BOOST_AUTO_TEST_CASE(forwardArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double re[] = { 999, 999, 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re+1, im+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(re[0], 999.0); + BOOST_CHECK_EQUAL(im[0], 999.0); + BOOST_CHECK_EQUAL(re[5], 999.0); + BOOST_CHECK_EQUAL(im[5], 999.0); + delete[] in; +} + +BOOST_AUTO_TEST_CASE(r_forwardArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double re[] = { 999, 999, 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999, 999, 999 }; + FFTReal(4).forward(in, re+1, im+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(re[0], 999.0); + BOOST_CHECK_EQUAL(im[0], 999.0); + BOOST_CHECK_EQUAL(re[5], 999.0); + BOOST_CHECK_EQUAL(im[5], 999.0); + delete[] in; +} + +BOOST_AUTO_TEST_CASE(inverseArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *re = new double[4]; + double *im = new double[4]; + re[0] = 0; + re[1] = 1; + re[2] = 0; + re[3] = 1; + im[0] = 0; + im[1] = -2; + im[2] = 0; + im[3] = 2; + double outre[] = { 999, 999, 999, 999, 999, 999 }; + double outim[] = { 999, 999, 999, 999, 999, 999 }; + FFT(4).process(true, re, im, outre+1, outim+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(outre[0], 999.0); + BOOST_CHECK_EQUAL(outim[0], 999.0); + BOOST_CHECK_EQUAL(outre[5], 999.0); + BOOST_CHECK_EQUAL(outim[5], 999.0); + delete[] re; + delete[] im; +} + +BOOST_AUTO_TEST_CASE(r_inverseArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *re = new double[3]; + double *im = new double[3]; + re[0] = 0; + re[1] = 1; + re[2] = 0; + im[0] = 0; + im[1] = -2; + im[2] = 0; + double outre[] = { 999, 999, 999, 999, 999, 999 }; + FFTReal(4).inverse(re, im, outre+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(outre[0], 999.0); + BOOST_CHECK_EQUAL(outre[5], 999.0); + delete[] re; + delete[] im; +} + +BOOST_AUTO_TEST_CASE(dc) +{ + // DC-only signal. The DC bin is purely real + double in[] = { 1, 1, 1, 1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 4.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_dc) +{ + // DC-only signal. The DC bin is purely real + double in[] = { 1, 1, 1, 1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 4.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(c_dc) +{ + // DC-only signal. The DC bin is purely real + double rin[] = { 1, 1, 1, 1 }; + double iin[] = { 1, 1, 1, 1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, rin, iin, re, im); + BOOST_CHECK_EQUAL(re[0], 4.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + BOOST_CHECK_EQUAL(im[0], 4.0); + BOOST_CHECK_EQUAL(im[1], 0.0); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_EQUAL(im[3], 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, rin); + COMPARE_ARRAY(backim, iin); +} + +BOOST_AUTO_TEST_CASE(sine) +{ + // Sine. Output is purely imaginary + double in[] = { 0, 1, 0, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + COMPARE_CONST(re, 0.0); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_EQUAL(im[1], -2.0); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_EQUAL(im[3], 2.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_sine) +{ + // Sine. Output is purely imaginary + double in[] = { 0, 1, 0, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + COMPARE_CONST(re, 0.0); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_EQUAL(im[1], -2.0); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_EQUAL(im[3], 2.0); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(cosine) +{ + // Cosine. Output is purely real + double in[] = { 1, 0, -1, 0 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 2.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 2.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_cosine) +{ + // Cosine. Output is purely real + double in[] = { 1, 0, -1, 0 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 2.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 2.0); + COMPARE_CONST(im, 0.0); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(c_cosine) +{ + // Cosine. Output is purely real + double rin[] = { 1, 0, -1, 0 }; + double iin[] = { 1, 0, -1, 0 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, rin, iin, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 2.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 2.0); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_EQUAL(im[1], 2.0); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_EQUAL(im[3], 2.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, rin); + COMPARE_ARRAY(backim, iin); +} + +BOOST_AUTO_TEST_CASE(sineCosine) +{ + // Sine and cosine mixed + double in[] = { 0.5, 1, -0.5, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_CLOSE(re[1], 1.0, 1e-12); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_CLOSE(re[3], 1.0, 1e-12); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_CLOSE(im[1], -2.0, 1e-12); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_CLOSE(im[3], 2.0, 1e-12); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_sineCosine) +{ + // Sine and cosine mixed + double in[] = { 0.5, 1, -0.5, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_CLOSE(re[1], 1.0, 1e-12); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_CLOSE(re[3], 1.0, 1e-12); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_CLOSE(im[1], -2.0, 1e-12); + BOOST_CHECK_EQUAL(im[2], 0.0); + BOOST_CHECK_CLOSE(im[3], 2.0, 1e-12); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(c_sineCosine) +{ + double rin[] = { 1, 0, -1, 0 }; + double iin[] = { 0, 1, 0, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, rin, iin, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 4.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, rin); + COMPARE_ARRAY(backim, iin); +} + +BOOST_AUTO_TEST_CASE(nyquist) +{ + double in[] = { 1, -1, 1, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 4.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_nyquist) +{ + double in[] = { 1, -1, 1, -1 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 4.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(dirac) +{ + double in[] = { 1, 0, 0, 0 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFT(4).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 1.0); + BOOST_CHECK_EQUAL(re[1], 1.0); + BOOST_CHECK_EQUAL(re[2], 1.0); + BOOST_CHECK_EQUAL(re[3], 1.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + FFT(4).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_dirac) +{ + double in[] = { 1, 0, 0, 0 }; + double re[] = { 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999 }; + FFTReal(4).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 1.0); + BOOST_CHECK_EQUAL(re[1], 1.0); + BOOST_CHECK_EQUAL(re[2], 1.0); + BOOST_CHECK_EQUAL(re[3], 1.0); + COMPARE_CONST(im, 0.0); + double back[4]; + // check conjugates are reconstructed + re[3] = 999; + im[3] = 999; + FFTReal(4).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(sizes) +{ + // Complex supports any size. A single test with an odd size + // will do here, without getting too much into our expectations + // about supported butterflies etc + + double in[] = { 1, 1, 1 }; + double re[] = { 999, 999, 999 }; + double im[] = { 999, 999, 999 }; + FFT(3).process(false, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 3.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + COMPARE_CONST(im, 0.0); + double back[3]; + double backim[3]; + FFT(3).process(true, re, im, back, backim); + COMPARE_ARRAY(back, in); + COMPARE_CONST(backim, 0.0); +} + +BOOST_AUTO_TEST_CASE(r_sizes) +{ + // Real supports any even size, but not odd ones + + BOOST_CHECK_THROW(FFTReal r(3), std::invalid_argument); + + double in[] = { 1, 1, 1, 1, 1, 1 }; + double re[] = { 999, 999, 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999, 999, 999 }; + FFTReal(6).forward(in, re, im); + BOOST_CHECK_EQUAL(re[0], 6.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(re[3], 0.0); + BOOST_CHECK_EQUAL(re[4], 0.0); + BOOST_CHECK_EQUAL(re[5], 0.0); + COMPARE_CONST(im, 0.0); + double back[6]; + FFTReal(6).inverse(re, im, back); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestMathUtilities.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,153 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "dsp/MathUtilities.h" + +#include <cmath> + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestMathUtilities) + +BOOST_AUTO_TEST_CASE(round) +{ + BOOST_CHECK_EQUAL(MathUtilities::round(0.5), 1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(0.49), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::round(0.99), 1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(0.01), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::round(0.0), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::round(100.0), 100.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-0.2), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-0.5), -1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-0.99), -1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-1.0), -1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-1.1), -1.0); + BOOST_CHECK_EQUAL(MathUtilities::round(-1.5), -2.0); +} + +BOOST_AUTO_TEST_CASE(mean) +{ + BOOST_CHECK_EQUAL(MathUtilities::mean(0, 0), 0); + double d0[] = { 0, 4, 3, -1 }; + BOOST_CHECK_EQUAL(MathUtilities::mean(d0, 4), 1.5); + double d1[] = { -2.6 }; + BOOST_CHECK_EQUAL(MathUtilities::mean(d1, 1), -2.6); + std::vector<double> v; + v.push_back(0); + v.push_back(4); + v.push_back(3); + v.push_back(-1); + BOOST_CHECK_EQUAL(MathUtilities::mean(v, 0, 4), 1.5); + BOOST_CHECK_EQUAL(MathUtilities::mean(v, 1, 2), 3.5); + BOOST_CHECK_EQUAL(MathUtilities::mean(v, 3, 1), -1); + BOOST_CHECK_EQUAL(MathUtilities::mean(v, 3, 0), 0); +} + +BOOST_AUTO_TEST_CASE(sum) +{ + BOOST_CHECK_EQUAL(MathUtilities::sum(0, 0), 0); + double d0[] = { 0, 4, 3, -1 }; + BOOST_CHECK_EQUAL(MathUtilities::sum(d0, 4), 6); + double d1[] = { -2.6 }; + BOOST_CHECK_EQUAL(MathUtilities::sum(d1, 1), -2.6); +} + +BOOST_AUTO_TEST_CASE(median) +{ + BOOST_CHECK_EQUAL(MathUtilities::median(0, 0), 0); + double d0[] = { 0, 4, 3, -1 }; + BOOST_CHECK_EQUAL(MathUtilities::median(d0, 4), 1.5); + double d1[] = { 0, 4, 3, -1, -1 }; + BOOST_CHECK_EQUAL(MathUtilities::median(d1, 5), 0); + double d2[] = { 1.0, -2.0 }; + BOOST_CHECK_EQUAL(MathUtilities::median(d2, 2), -0.5); + double d3[] = { -2.6 }; + BOOST_CHECK_EQUAL(MathUtilities::median(d3, 1), -2.6); +} + +BOOST_AUTO_TEST_CASE(princarg) +{ + BOOST_CHECK_EQUAL(MathUtilities::princarg(M_PI), M_PI); + BOOST_CHECK_EQUAL(MathUtilities::princarg(-M_PI), M_PI); + BOOST_CHECK_EQUAL(MathUtilities::princarg(2 * M_PI), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::princarg(5 * M_PI), M_PI); + BOOST_CHECK_EQUAL(MathUtilities::princarg(1.0), 1.0); + BOOST_CHECK_EQUAL(MathUtilities::princarg(-1.0), -1.0); + BOOST_CHECK_EQUAL(MathUtilities::princarg(-10.0), -10.0 + 4 * M_PI); +} + +BOOST_AUTO_TEST_CASE(isPowerOfTwo) +{ + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(0), false); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(1), true); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(-2), false); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(2), true); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(3), false); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(12), false); + BOOST_CHECK_EQUAL(MathUtilities::isPowerOfTwo(16), true); +} + +BOOST_AUTO_TEST_CASE(nextPowerOfTwo) +{ + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(0), 1); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(1), 1); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(-2), 1); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(2), 2); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(3), 4); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(12), 16); + BOOST_CHECK_EQUAL(MathUtilities::nextPowerOfTwo(16), 16); +} + +BOOST_AUTO_TEST_CASE(previousPowerOfTwo) +{ + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(0), 1); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(1), 1); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(-2), 1); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(2), 2); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(3), 2); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(12), 8); + BOOST_CHECK_EQUAL(MathUtilities::previousPowerOfTwo(16), 16); +} + +BOOST_AUTO_TEST_CASE(nearestPowerOfTwo) +{ + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(0), 1); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(1), 1); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(-2), 1); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(2), 2); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(3), 4); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(11), 8); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(12), 16); + BOOST_CHECK_EQUAL(MathUtilities::nearestPowerOfTwo(16), 16); +} + +BOOST_AUTO_TEST_CASE(factorial) +{ + BOOST_CHECK_EQUAL(MathUtilities::factorial(-10), 0.0); + BOOST_CHECK_EQUAL(MathUtilities::factorial(0), 1.0); + BOOST_CHECK_EQUAL(MathUtilities::factorial(1), 1.0); + BOOST_CHECK_EQUAL(MathUtilities::factorial(2), 2.0); + BOOST_CHECK_EQUAL(MathUtilities::factorial(3), 6.0); + BOOST_CHECK_EQUAL(MathUtilities::factorial(4), 24.0); + + // Too big for an int, hence double return value from factorial + BOOST_CHECK_EQUAL(MathUtilities::factorial(20), 2432902008176640000.0); +} + +BOOST_AUTO_TEST_CASE(gcd) +{ + BOOST_CHECK_EQUAL(MathUtilities::gcd(1, 1), 1); + BOOST_CHECK_EQUAL(MathUtilities::gcd(2, 1), 1); + BOOST_CHECK_EQUAL(MathUtilities::gcd(2, 3), 1); + BOOST_CHECK_EQUAL(MathUtilities::gcd(4, 2), 2); + BOOST_CHECK_EQUAL(MathUtilities::gcd(18, 24), 6); + BOOST_CHECK_EQUAL(MathUtilities::gcd(27, 18), 9); + BOOST_CHECK_EQUAL(MathUtilities::gcd(18, 36), 18); + BOOST_CHECK_EQUAL(MathUtilities::gcd(37, 18), 1); +} + +BOOST_AUTO_TEST_SUITE_END() + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestResampler.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,315 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "dsp/Resampler.h" + +#include "dsp/Window.h" +#include "dsp/FFT.h" + +#include <iostream> + +#include <cmath> + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestResampler) + +using std::cout; +using std::endl; +using std::vector; + +void +testResamplerOneShot(int sourceRate, + int targetRate, + int n, + double *in, + int m, + double *expected, + int skip) +{ + vector<double> resampled = Resampler::resample(sourceRate, targetRate, + in, n); + if (skip == 0) { + BOOST_CHECK_EQUAL(resampled.size(), m); + } + for (int i = 0; i < m; ++i) { + BOOST_CHECK_SMALL(resampled[i + skip] - expected[i], 1e-6); + } +} + +void +testResampler(int sourceRate, + int targetRate, + int n, + double *in, + int m, + double *expected) +{ + // Here we provide the input in chunks (of varying size) + + Resampler r(sourceRate, targetRate); + int latency = r.getLatency(); + + int m1 = m + latency; + int n1 = int((m1 * sourceRate) / targetRate); + + double *inPadded = new double[n1]; + double *outPadded = new double[m1]; + + for (int i = 0; i < n1; ++i) { + if (i < n) inPadded[i] = in[i]; + else inPadded[i] = 0.0; + } + + for (int i = 0; i < m1; ++i) { + outPadded[i] = -999.0; + } + + int chunkSize = 1; + int got = 0; + int i = 0; + + while (true) { + got += r.process(inPadded + i, outPadded + got, chunkSize); + i = i + chunkSize; + chunkSize = chunkSize + 1; + if (i >= n1) { + break; + } else if (i + chunkSize >= n1) { + chunkSize = n1 - i; + } else if (chunkSize > 15) { + chunkSize = 1; + } + } + + BOOST_CHECK_EQUAL(got, m1); + + for (int i = latency; i < m1; ++i) { + BOOST_CHECK_SMALL(outPadded[i] - expected[i-latency], 1e-8); + } + + delete[] outPadded; + delete[] inPadded; +} + +BOOST_AUTO_TEST_CASE(sameRateOneShot) +{ + double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; + testResamplerOneShot(4, 4, 10, d, 10, d, 0); +} + +BOOST_AUTO_TEST_CASE(sameRate) +{ + double d[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; + testResampler(4, 4, 10, d, 10, d); +} + +BOOST_AUTO_TEST_CASE(interpolatedMisc) +{ + // Interpolating any signal by N should give a signal in which + // every Nth sample is the original signal + double in[] = { 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 }; + int n = sizeof(in)/sizeof(in[0]); + for (int factor = 2; factor < 10; ++factor) { + vector<double> out = Resampler::resample(6, 6 * factor, in, n); + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(out[i * factor] - in[i], 1e-5); + } + } +} + +BOOST_AUTO_TEST_CASE(interpolatedSine) +{ + // Interpolating a sinusoid should give us a sinusoid, once we've + // dropped the first few samples + double in[1000]; + double out[2000]; + for (int i = 0; i < 1000; ++i) { + in[i] = sin(i * M_PI / 2.0); + } + for (int i = 0; i < 2000; ++i) { + out[i] = sin(i * M_PI / 4.0); + } + testResamplerOneShot(8, 16, 1000, in, 200, out, 512); +} + +BOOST_AUTO_TEST_CASE(decimatedSine) +{ + // Decimating a sinusoid should give us a sinusoid, once we've + // dropped the first few samples + double in[2000]; + double out[1000]; + for (int i = 0; i < 2000; ++i) { + in[i] = sin(i * M_PI / 8.0); + } + for (int i = 0; i < 1000; ++i) { + out[i] = sin(i * M_PI / 4.0); + } + testResamplerOneShot(16, 8, 2000, in, 200, out, 256); +} + +double +measureSinFreq(const vector<double> &v, int rate, int countCycles) +{ + int n = v.size(); + int firstPeak = -1; + int lastPeak = -1; + int nPeaks = 0; + // count +ve peaks + for (int i = v.size()/4; i + 1 < n; ++i) { + // allow some fuzz + int x0 = int(10000 * v[i-1]); + int x1 = int(10000 * v[i]); + int x2 = int(10000 * v[i+1]); + if (x1 > 0 && x1 > x0 && x1 >= x2) { + if (firstPeak < 0) firstPeak = i; + lastPeak = i; + ++nPeaks; + if (nPeaks == countCycles) break; + } + } + int nCycles = nPeaks - 1; + if (nCycles <= 0) return 0.0; + double cycle = double(lastPeak - firstPeak) / nCycles; +// cout << "lastPeak = " << lastPeak << ", firstPeak = " << firstPeak << ", dist = " << lastPeak - firstPeak << ", nCycles = " << nCycles << ", cycle = " << cycle << endl; + return rate / cycle; +} + +void +testSinFrequency(int freq, + int sourceRate, + int targetRate) +{ + // Resampling a sinusoid and then resampling back should give us a + // sinusoid of the same frequency as we started with + + int nCycles = 500; + + int duration = int(nCycles * float(sourceRate) / float(freq)); +// cout << "freq = " << freq << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", duration = " << duration << endl; + + vector<double> in(duration, 0); + for (int i = 0; i < duration; ++i) { + in[i] = sin(i * M_PI * 2.0 * freq / sourceRate); + } + + vector<double> out = Resampler::resample(sourceRate, targetRate, + in.data(), in.size()); + + vector<double> back = Resampler::resample(targetRate, sourceRate, + out.data(), out.size()); + + BOOST_CHECK_EQUAL(in.size(), back.size()); + + double inFreq = measureSinFreq(in, sourceRate, nCycles / 2); + double backFreq = measureSinFreq(back, sourceRate, nCycles / 2); + + BOOST_CHECK_SMALL(inFreq - backFreq, 1e-8); +} + +// In each of the following we use a frequency that has an exact cycle +// length in samples at the lowest sample rate, so that we can easily +// rule out errors in measuring the cycle length after resampling. If +// the resampler gets its input or output rate wrong, that will show +// up no matter what the test signal's initial frequency is. + +BOOST_AUTO_TEST_CASE(downUp2) +{ + testSinFrequency(441, 44100, 22050); +} + +BOOST_AUTO_TEST_CASE(downUp5) +{ + testSinFrequency(300, 15000, 3000); +} + +BOOST_AUTO_TEST_CASE(downUp16) +{ + testSinFrequency(300, 48000, 3000); +} + +BOOST_AUTO_TEST_CASE(upDown2) +{ + testSinFrequency(441, 44100, 88200); +} + +BOOST_AUTO_TEST_CASE(upDown5) +{ + testSinFrequency(300, 3000, 15000); +} + +BOOST_AUTO_TEST_CASE(upDown16) +{ + testSinFrequency(300, 3000, 48000); +} + +vector<double> +squareWave(int rate, double freq, int n) +{ + //!!! todo: hoist, test + vector<double> v(n, 0.0); + for (int h = 0; h < (rate/4)/freq; ++h) { + double m = h * 2 + 1; + double scale = 1.0 / m; + for (int i = 0; i < n; ++i) { + double s = scale * sin((i * 2.0 * M_PI * m * freq) / rate); + v[i] += s; + } + } + return v; +} + +void +testSpectrum(int inrate, int outrate) +{ + // One second of a square wave + int freq = 500; + + vector<double> square = + squareWave(inrate, freq, inrate); + + vector<double> maybeSquare = + Resampler::resample(inrate, outrate, square.data(), square.size()); + + BOOST_CHECK_EQUAL(maybeSquare.size(), outrate); + + Window<double>(HanningWindow, inrate).cut(square.data()); + Window<double>(HanningWindow, outrate).cut(maybeSquare.data()); + + // forward magnitude with size inrate, outrate + + vector<double> inSpectrum(inrate, 0.0); + FFTReal(inrate).forwardMagnitude(square.data(), inSpectrum.data()); + for (int i = 0; i < (int)inSpectrum.size(); ++i) { + inSpectrum[i] /= inrate; + } + + vector<double> outSpectrum(outrate, 0.0); + FFTReal(outrate).forwardMagnitude(maybeSquare.data(), outSpectrum.data()); + for (int i = 0; i < (int)outSpectrum.size(); ++i) { + outSpectrum[i] /= outrate; + } + + // Don't compare bins any higher than 96% of Nyquist freq of lower sr + int lengthOfInterest = (inrate < outrate ? inrate : outrate) / 2; + lengthOfInterest = lengthOfInterest - (lengthOfInterest / 25); + + for (int i = 0; i < lengthOfInterest; ++i) { + BOOST_CHECK_SMALL(inSpectrum[i] - outSpectrum[i], 1e-7); + } +} +/* +BOOST_AUTO_TEST_CASE(spectrum) +{ + int rates[] = { 8000, 22050, 44100, 48000 }; + for (int i = 0; i < (int)(sizeof(rates)/sizeof(rates[0])); ++i) { + for (int j = 0; j < (int)(sizeof(rates)/sizeof(rates[0])); ++j) { + testSpectrum(rates[i], rates[j]); + } + } +} +*/ +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/TestWindow.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,202 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "dsp/Window.h" +#include "dsp/KaiserWindow.h" +#include "dsp/SincWindow.h" + +#include <iostream> + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestWindow) + +using std::cout; +using std::endl; + +#define COMPARE_ARRAY(a, b) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-4); \ + } + +void +testSymmetric(double *d, int n) +{ + for (int i = 0; i <= n/2; ++i) { + BOOST_CHECK_CLOSE(d[i], d[n-i-1], 1e-10); + } +} + +BOOST_AUTO_TEST_CASE(periodic) +{ + // We can't actually test whether a function is periodic, given + // only one cycle of it! But we can make sure that all but the + // first sample is symmetric, which is what a symmetric window + // becomes when generated in periodic mode + double d[9]; + for (int n = 8; n <= 9; ++n) { + for (int wt = (int)FirstWindow; wt <= (int)LastWindow; ++wt) { + for (int i = 0; i < n; ++i) d[i] = 1.0; + Window<double> w((WindowType)wt, n); + w.cut(d); + testSymmetric(d + 1, n - 1); + } + } +} + +template <int N> +void testWindow(WindowType type, const double expected[N]) +{ + double d[N]; + for (int i = 0; i < N; ++i) d[i] = 1.0; + Window<double> w(type, N); + w.cut(d); + COMPARE_ARRAY(d, expected); + + double d0[N], d1[N]; + for (int i = 0; i < N; ++i) d0[i] = 0.5 + (1.0 / (N * 2)) * (i + 1); + w.cut(d0, d1); + for (int i = 0; i < N; ++i) { + BOOST_CHECK_SMALL(d1[i] - d0[i] * expected[i], 1e-4); + } +} + +BOOST_AUTO_TEST_CASE(bartlett) +{ + double e1[] = { 1 }; + testWindow<1>(BartlettWindow, e1); + + double e2[] = { 0, 0 }; + testWindow<2>(BartlettWindow, e2); + + double e3[] = { 0, 2./3., 2./3. }; + testWindow<3>(BartlettWindow, e3); + + double e4[] = { 0, 1./2., 1., 1./2. }; + testWindow<4>(BartlettWindow, e4); + + double e5[] = { 0, 1./2., 1., 1., 1./2. }; + testWindow<5>(BartlettWindow, e5); + + double e6[] = { 0, 1./3., 2./3., 1., 2./3., 1./3. }; + testWindow<6>(BartlettWindow, e6); +} + +BOOST_AUTO_TEST_CASE(hamming) +{ + double e1[] = { 1 }; + testWindow<1>(HammingWindow, e1); + + double e10[] = { + 0.0800, 0.1679, 0.3979, 0.6821, 0.9121, + 1.0000, 0.9121, 0.6821, 0.3979, 0.1679 + }; + testWindow<10>(HammingWindow, e10); +} + +BOOST_AUTO_TEST_CASE(hann) +{ + double e1[] = { 1 }; + testWindow<1>(HanningWindow, e1); + + double e10[] = { + 0, 0.0955, 0.3455, 0.6545, 0.9045, + 1.0000, 0.9045, 0.6545, 0.3455, 0.0955, + }; + testWindow<10>(HanningWindow, e10); +} + +BOOST_AUTO_TEST_CASE(blackman) +{ + double e1[] = { 1 }; + testWindow<1>(BlackmanWindow, e1); + + double e10[] = { + 0, 0.0402, 0.2008, 0.5098, 0.8492, + 1.0000, 0.8492, 0.5098, 0.2008, 0.0402, + }; + testWindow<10>(BlackmanWindow, e10); +} + +BOOST_AUTO_TEST_CASE(blackmanHarris) +{ + double e1[] = { 1 }; + testWindow<1>(BlackmanHarrisWindow, e1); + + double e10[] = { + 0.0001, 0.0110, 0.1030, 0.3859, 0.7938, + 1.0000, 0.7938, 0.3859, 0.1030, 0.0110, + }; + testWindow<10>(BlackmanHarrisWindow, e10); +} + +BOOST_AUTO_TEST_CASE(kaiser_4_10) +{ + double d[10]; + for (int i = 0; i < 10; ++i) d[i] = 1.0; + double e[] = { + 0.0885, 0.2943, 0.5644, 0.8216, 0.9789, + 0.9789, 0.8216, 0.5644, 0.2943, 0.0885 + }; + KaiserWindow::Parameters p; + p.length = 10; + p.beta = 4; + KaiserWindow k(p); + k.cut(d); + COMPARE_ARRAY(d, e); +} + +BOOST_AUTO_TEST_CASE(kaiser_2p5_11) +{ + double d[11]; + for (int i = 0; i < 11; ++i) d[i] = 1.0; + double e[] = { + 0.3040, 0.5005, 0.6929, 0.8546, 0.9622, + 1.0000, 0.9622, 0.8546, 0.6929, 0.5005, 0.3040 + }; + KaiserWindow::Parameters p; + p.length = 11; + p.beta = 2.5; + KaiserWindow k(p); + k.cut(d); + COMPARE_ARRAY(d, e); +} + +//!!! todo: tests for kaiser with attenuation and bandwidth parameters + +template <int N> +void testSinc(double p, const double expected[N]) +{ + double d[N]; + for (int i = 0; i < N; ++i) d[i] = 1.0; + SincWindow w(N, p); + w.cut(d); + COMPARE_ARRAY(d, expected); + + double d0[N], d1[N]; + for (int i = 0; i < N; ++i) d0[i] = 0.5 + (1.0 / (N * 2)) * (i + 1); + w.cut(d0, d1); + for (int i = 0; i < N; ++i) { + BOOST_CHECK_SMALL(d1[i] - d0[i] * expected[i], 1e-4); + } +} + +BOOST_AUTO_TEST_CASE(sinc) +{ + double e1[] = { 0, 0, 1, 0, 0 }; + testSinc<5>(1, e1); + + double e2[] = { 0, 0, 1, 0, 0 }; + testSinc<5>(2, e2); + + double e3[] = { -0.2122, 0.0, 0.6366, 1.0, 0.6366, 0, -0.2122 }; + testSinc<7>(4, e3); + + double e4[] = { -0.2122, 0, 0.6366, 1, 0.6366, 0 }; + testSinc<6>(4, e4); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/processfile.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,295 @@ + +#include "ConstantQ.h" +#include "CQInverse.h" + +#include <sndfile.h> + +#include <iostream> + +using std::vector; +using std::cerr; +using std::endl; + +#include <cstring> + +#include <getopt.h> +#include <unistd.h> +#include <sys/time.h> +#include <cstdlib> + +int main(int argc, char **argv) +{ + double maxFreq = 0; + double minFreq = 0; + int bpo = 0; + bool help = false; + + int c; + + while (1) { + int optionIndex = 0; + + static struct option longOpts[] = { + { "help", 0, 0, 'h', }, + { "maxfreq", 1, 0, 'x', }, + { "minfreq", 1, 0, 'n', }, + { "bpo", 1, 0, 'b' }, + { 0, 0, 0, 0 }, + }; + + c = getopt_long(argc, argv, + "hx:n:b:", + longOpts, &optionIndex); + if (c == -1) break; + + switch (c) { + case 'h': help = true; break; + case 'x': maxFreq = atof(optarg); break; + case 'n': minFreq = atof(optarg); break; + case 'b': bpo = atoi(optarg); break; + default: help = true; break; + } + } + + if (help || (optind + 2 != argc && optind + 3 != argc)) { + cerr << endl; + cerr << "Usage: " << argv[0] << " [options] infile.wav outfile.wav [differencefile.wav]" << endl; + cerr << endl; + cerr << "Options:" << endl; + cerr << " -x<X>, --maxfreq <X> Maximum frequency (default = sample rate / 3)" << endl; + cerr << " -n<X>, --minfreq <X> Minimum frequency (default = 100, actual min may vary)" << endl; + cerr << " -b<X>, --bpo <X> Bins per octave (default = 60)" << endl; + cerr << " -h, --help Print this help" << endl; + cerr << endl; + cerr << "This rather useless program simply performs a forward Constant-Q transform with" << endl; + cerr << "the requested parameters, followed by its inverse, and writes the result to the" << endl; + cerr << "output file. If a diff file name is provided, the difference between input and" << endl; + cerr << "output signals is also written to that. All this accomplishes is to produce a" << endl; + cerr << "signal that approximates the input: it's intended for test purposes only." << endl; + cerr << endl; + cerr << "(Want to calculate and obtain a Constant-Q spectrogram? Use the CQVamp plugin" << endl; + cerr << "in a Vamp plugin host.)" << endl; + cerr << endl; + return 2; + } + + char *fileName = strdup(argv[optind++]); + char *fileNameOut = strdup(argv[optind++]); + char *diffFileName = (optind < argc ? strdup(argv[optind++]) : 0); + bool doDiff = (diffFileName != 0); + + SNDFILE *sndfile; + SNDFILE *sndfileOut; + SNDFILE *sndDiffFile = 0; + SF_INFO sfinfo; + SF_INFO sfinfoOut; + SF_INFO sfinfoDiff; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(fileName, SFM_READ, &sfinfo); + if (!sndfile) { + cerr << "ERROR: Failed to open input file \"" << fileName << "\": " + << sf_strerror(sndfile) << endl; + return 1; + } + + sfinfoOut.channels = 1; + sfinfoOut.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + sfinfoOut.frames = sfinfo.frames; + sfinfoOut.samplerate = sfinfo.samplerate; + sfinfoOut.sections = sfinfo.sections; + sfinfoOut.seekable = sfinfo.seekable; + + sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ; + if (!sndfileOut) { + cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: " + << sf_strerror(sndfileOut) << endl; + return 1; + } + + if (doDiff) { + sfinfoDiff = sfinfoOut; + sndDiffFile = sf_open(diffFileName, SFM_WRITE, &sfinfoDiff); + if (!sndDiffFile) { + cerr << "ERROR: Failed to open diff output file \"" << diffFileName << "\" for writing: " + << sf_strerror(sndDiffFile) << endl; + return 1; + } + } + + int ibs = 1024; + int channels = sfinfo.channels; + float *fbuf = new float[channels * ibs]; + + if (maxFreq == 0.0) maxFreq = sfinfo.samplerate / 3; + if (minFreq == 0.0) minFreq = 100; + if (bpo == 0) bpo = 60; + + CQParameters params(sfinfo.samplerate, minFreq, maxFreq, bpo); + ConstantQ cq(params); + CQInverse cqi(params); + + cerr << "max freq = " << cq.getMaxFrequency() << ", min freq = " + << cq.getMinFrequency() << ", octaves = " << cq.getOctaves() << endl; + + cerr << "octave boundaries: "; + for (int i = 0; i < cq.getOctaves(); ++i) { + cerr << cq.getMaxFrequency() / pow(2, i) << " "; + } + cerr << endl; + + int inframe = 0; + int outframe = 0; + int latency = cq.getLatency() + cqi.getLatency(); + + vector<double> buffer; + + double maxdiff = 0.0; + int maxdiffidx = 0; + + cerr << "forward latency = " << cq.getLatency() << ", inverse latency = " + << cqi.getLatency() << ", total = " << latency << endl; + + timeval tv; + (void)gettimeofday(&tv, 0); + + while (inframe < sfinfo.frames) { + + int count = -1; + + if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) { + break; + } + + vector<double> cqin; + for (int i = 0; i < count; ++i) { + double v = fbuf[i * channels]; + if (channels > 1) { + for (int c = 1; c < channels; ++c) { + v += fbuf[i * channels + c]; + } + v /= channels; + } + cqin.push_back(v); + } + + if (doDiff) { + buffer.insert(buffer.end(), cqin.begin(), cqin.end()); + } + + vector<double> cqout = cqi.process(cq.process(cqin)); + + for (int i = 0; i < int(cqout.size()); ++i) { + if (cqout[i] > 1.0) cqout[i] = 1.0; + if (cqout[i] < -1.0) cqout[i] = -1.0; + } + + if (outframe >= latency) { + + sf_writef_double(sndfileOut, + cqout.data(), + cqout.size()); + + } else if (outframe + (int)cqout.size() >= latency) { + + int offset = latency - outframe; + sf_writef_double(sndfileOut, + cqout.data() + offset, + cqout.size() - offset); + } + + if (doDiff) { + for (int i = 0; i < (int)cqout.size(); ++i) { + if (outframe + i >= latency) { + int dframe = outframe + i - latency; + if (dframe >= (int)buffer.size()) cqout[i] = 0; + else cqout[i] -= buffer[dframe]; + if (fabs(cqout[i]) > maxdiff && + dframe > sfinfo.samplerate && // ignore first/last sec + dframe + sfinfo.samplerate < sfinfo.frames) { + maxdiff = fabs(cqout[i]); + maxdiffidx = dframe; + } + } + } + + if (outframe >= latency) { + + sf_writef_double(sndDiffFile, + cqout.data(), + cqout.size()); + + } else if (outframe + (int)cqout.size() >= latency) { + + int offset = latency - outframe; + sf_writef_double(sndDiffFile, + cqout.data() + offset, + cqout.size() - offset); + } + } + + inframe += count; + outframe += cqout.size(); + } + + vector<double> r = cqi.process(cq.getRemainingOutput()); + vector<double> r2 = cqi.getRemainingOutput(); + + r.insert(r.end(), r2.begin(), r2.end()); + + for (int i = 0; i < int(r.size()); ++i) { + if (r[i] > 1.0) r[i] = 1.0; + if (r[i] < -1.0) r[i] = -1.0; + } + + sf_writef_double(sndfileOut, r.data(), r.size()); + if (doDiff) { + for (int i = 0; i < (int)r.size(); ++i) { + if (outframe + i >= latency) { + int dframe = outframe + i - latency; + if (dframe >= (int)buffer.size()) r[i] = 0; + else r[i] -= buffer[dframe]; + if (fabs(r[i]) > maxdiff && + dframe > sfinfo.samplerate && // ignore first/last sec + dframe + sfinfo.samplerate < sfinfo.frames) { + maxdiff = fabs(r[i]); + maxdiffidx = dframe; + } + } + } + sf_writef_double(sndDiffFile, r.data(), r.size()); + } + outframe += r.size(); + + sf_close(sndfile); + sf_close(sndfileOut); + + if (doDiff) { + sf_close(sndDiffFile); + } + + cerr << "in: " << inframe << ", out: " << outframe - latency << endl; + + if (doDiff) { + double db = 10 * log10(maxdiff); + cerr << "max diff [excluding first and last second of audio] is " + << maxdiff << " (" << db << " dBFS)" + << " at sample index " << maxdiffidx << endl; + } + + timeval etv; + (void)gettimeofday(&etv, 0); + + etv.tv_sec -= tv.tv_sec; + if (etv.tv_usec < tv.tv_usec) { + etv.tv_usec += 1000000; + etv.tv_sec -= 1; + } + etv.tv_usec -= tv.tv_usec; + + double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0); + cerr << "elapsed time (not counting init): " << sec << " sec, frames/sec at input: " << inframe/sec << endl; + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/test/test-inverse.sh Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,45 @@ +#!/bin/bash + +# Test that the forward-inverse CQ transform produces the same output +# (to a given noise level, and within the appropriate frequency range) +# as its input. +# +# This requires the program "processfile" to be compiled and in the +# same directory, and an audio file filtered-whitenoise-480-14600.wav +# to be in the subdir "data". The audio file contains white noise +# band-limited to the 480-14600Hz range. This is fed as input to a +# forward-inverse CQ chain restricted to the range 465-14700 Hz (5 +# octaves); the processfile program calculates the output and performs +# a sample-by-sample diff against the input. We then check that the +# diff is below a suitable noise floor. + +mydir=`dirname "$0"` + +process="$mydir/processfile" +if [ ! -x "$process" ]; then + echo "ERROR: $mydir/processfile not found or not executable" + exit 1 +fi +infile="$mydir/data/filtered-whitenoise-480-14600.wav" +if [ ! -f "$infile" ]; then + echo "ERROR: Test file $infile not found" + exit 1 +fi + +outfile="/tmp/$$.out.wav" +difffile="/tmp/$$.diff.wav" +logfile="/tmp/$$.log.txt" +trap "rm -f ${outfile} ${difffile} ${logfile}" 0 + +"$process" -x 14700 -n 465 -b 36 "$infile" "$outfile" "$difffile" 2>&1 | tee "$logfile" || exit 1 + +int_db=`grep 'max diff' "$logfile" | sed 's/^[^(]*(//' | sed 's/[^0-9-].*//'` +good=`expr "$int_db" "<" "-20"` +if [ "$good" == "1" ]; then + echo "Forward-inverse process is satisfactory" + exit 0 +else + echo "Forward-inverse not OK: Rounded dB value $int_db is too high -- should be < -20" + exit 1 +fi +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/vamp/CQChromaVamp.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,330 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "CQChromaVamp.h" + +#include "cq/Chromagram.h" + +#include <algorithm> + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +static const int defaultLowestOctave = 0; +static const int defaultOctaveCount = 7; +static const int defaultBPO = 36; +static const float defaultTuningFrequency = 440.f; + +CQChromaVamp::CQChromaVamp(float inputSampleRate) : + Vamp::Plugin(inputSampleRate), + m_lowestOctave(defaultLowestOctave), + m_octaveCount(defaultOctaveCount), + m_tuningFrequency(defaultTuningFrequency), + m_bpo(defaultBPO), + m_chroma(0), + m_haveStartTime(false), + m_columnCount(0) +{ +} + +CQChromaVamp::~CQChromaVamp() +{ + delete m_chroma; +} + +string +CQChromaVamp::getIdentifier() const +{ + return "cqchromavamp"; +} + +string +CQChromaVamp::getName() const +{ + return "CQ Chromagram"; +} + +string +CQChromaVamp::getDescription() const +{ + return "Extract a Constant-Q spectrogram with constant ratio of centre frequency to resolution from the audio, then wrap it around into a single-octave chromagram."; +} + +string +CQChromaVamp::getMaker() const +{ + return "Queen Mary, University of London"; +} + +int +CQChromaVamp::getPluginVersion() const +{ + return 2; +} + +string +CQChromaVamp::getCopyright() const +{ + return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2015 QMUL. BSD/MIT licence."; +} + +CQChromaVamp::ParameterList +CQChromaVamp::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor desc; + + desc.identifier = "lowestoct"; + desc.name = "Lowest Contributing Octave"; + desc.unit = ""; + desc.description = "Octave number of the lowest octave to include in the chromagram. Octave numbering is ASA standard, with -1 as the first octave in the MIDI range and middle-C being C4. The octave starts at C."; + desc.minValue = -1; + desc.maxValue = 12; + desc.defaultValue = defaultLowestOctave; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "octaves"; + desc.name = "Contributing Octave Count"; + desc.unit = "octaves"; + desc.description = "Number of octaves to use when generating the Constant-Q transform. All octaves are wrapped around and summed to produce a single octave chromagram as output."; + desc.minValue = 1; + desc.maxValue = 12; + desc.defaultValue = defaultOctaveCount; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "tuning"; + desc.name = "Tuning Frequency"; + desc.unit = "Hz"; + desc.description = "Frequency of concert A"; + desc.minValue = 360; + desc.maxValue = 500; + desc.defaultValue = defaultTuningFrequency; + desc.isQuantized = false; + list.push_back(desc); + + desc.identifier = "bpo"; + desc.name = "Bins per Octave"; + desc.unit = "bins"; + desc.description = "Number of constant-Q transform bins per octave"; + desc.minValue = 2; + desc.maxValue = 480; + desc.defaultValue = defaultBPO; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + return list; +} + +float +CQChromaVamp::getParameter(std::string param) const +{ + if (param == "lowestoct") { + return m_lowestOctave; + } + if (param == "octaves") { + return m_octaveCount; + } + if (param == "tuning") { + return m_tuningFrequency; + } + if (param == "bpo") { + return m_bpo; + } + std::cerr << "WARNING: CQChromaVamp::getParameter: unknown parameter \"" + << param << "\"" << std::endl; + return 0.0; +} + +void +CQChromaVamp::setParameter(std::string param, float value) +{ + if (param == "lowestoct") { + m_lowestOctave = int(value + 0.5f); + } else if (param == "octaves") { + m_octaveCount = int(value + 0.5f); + } else if (param == "tuning") { + m_tuningFrequency = value; + } else if (param == "bpo") { + m_bpo = int(value + 0.5f); + } else { + std::cerr << "WARNING: CQChromaVamp::setParameter: unknown parameter \"" + << param << "\"" << std::endl; + } +} + +bool +CQChromaVamp::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (m_chroma) { + delete m_chroma; + m_chroma = 0; + } + + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = stepSize; + m_blockSize = blockSize; + + reset(); + + if (!m_chroma || !m_chroma->isValid()) { + cerr << "CQVamp::initialise: Constant-Q parameters not valid! Not initialising" << endl; + return false; + } + + return true; +} + +void +CQChromaVamp::reset() +{ + delete m_chroma; + Chromagram::Parameters p(m_inputSampleRate); + p.lowestOctave = m_lowestOctave; + p.octaveCount = m_octaveCount; + p.binsPerOctave = m_bpo; + p.tuningFrequency = m_tuningFrequency; + + m_chroma = new Chromagram(p); + + m_haveStartTime = false; + m_startTime = Vamp::RealTime::zeroTime; + m_columnCount = 0; +} + +size_t +CQChromaVamp::getPreferredStepSize() const +{ + return 0; +} + +size_t +CQChromaVamp::getPreferredBlockSize() const +{ + return 0; +} + +CQChromaVamp::OutputList +CQChromaVamp::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "chromagram"; + d.name = "Chromagram"; + d.unit = ""; + d.description = "Chromagram obtained from output of constant-Q transform, folding over each process block into a single-octave vector"; + d.hasFixedBinCount = true; + d.binCount = m_bpo; + + if (m_chroma) { + for (int i = 0; i < (int)d.binCount; ++i) { + d.binNames.push_back(m_chroma->getBinName(i)); + } + } + + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = m_inputSampleRate / (m_chroma ? m_chroma->getColumnHop() : 256); + list.push_back(d); + + return list; +} + +CQChromaVamp::FeatureSet +CQChromaVamp::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (!m_chroma) { + cerr << "ERROR: CQChromaVamp::process: " + << "Plugin has not been initialised" + << endl; + return FeatureSet(); + } + + if (!m_haveStartTime) { + m_startTime = timestamp; + m_haveStartTime = true; + } + + vector<double> data; + for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]); + + vector<vector<double> > chromaout = m_chroma->process(data); + return convertToFeatures(chromaout); +} + +CQChromaVamp::FeatureSet +CQChromaVamp::getRemainingFeatures() +{ + vector<vector<double> > chromaout = m_chroma->getRemainingOutput(); + return convertToFeatures(chromaout); +} + +CQChromaVamp::FeatureSet +CQChromaVamp::convertToFeatures(const vector<vector<double> > &chromaout) +{ + FeatureSet returnFeatures; + + int width = chromaout.size(); + + for (int i = 0; i < width; ++i) { + + vector<float> column(chromaout[i].begin(), chromaout[i].end()); + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime + (m_columnCount * m_chroma->getColumnHop() - m_chroma->getLatency(), + m_inputSampleRate); + feature.values = column; + feature.label = ""; + + if (feature.timestamp >= m_startTime) { + returnFeatures[0].push_back(feature); + } + + ++m_columnCount; + } + + return returnFeatures; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/vamp/CQChromaVamp.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQCHROMAVAMP_H +#define CQCHROMAVAMP_H + +#include <vamp-sdk/Plugin.h> + +class Chromagram; + +class CQChromaVamp : public Vamp::Plugin +{ +public: + CQChromaVamp(float inputSampleRate); + virtual ~CQChromaVamp(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + int m_lowestOctave; + int m_octaveCount; + float m_tuningFrequency; + int m_bpo; + + Chromagram *m_chroma; + int m_stepSize; + int m_blockSize; + + Vamp::RealTime m_startTime; + bool m_haveStartTime; + int m_columnCount; + + FeatureSet convertToFeatures(const std::vector<std::vector<double> > &); +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/vamp/CQVamp.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,444 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "CQVamp.h" + +#include "Pitch.h" + +#include <algorithm> +#include <cstdio> + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +// The plugin offers either MIDI pitch or frequency range parameters, +// depending on the midiPitchParameters option given to the +// constructor. It never offers both. So they can have different +// defaults; if we're using MIDI pitch, the min and max frequencies +// will come from those rather than from the m_minFrequency and +// m_maxFrequency members. +static const int defaultMinMIDIPitch = 36; +static const int defaultMaxMIDIPitch = 96; +static const int defaultBPO = 36; +static const float defaultMinFrequency = 110; +static const float defaultMaxFrequency = 14700; +static const float defaultTuningFrequency = 440.f; + +CQVamp::CQVamp(float inputSampleRate, bool midiPitchParameters) : + Vamp::Plugin(inputSampleRate), + m_midiPitchParameters(midiPitchParameters), + m_minMIDIPitch(defaultMinMIDIPitch), + m_maxMIDIPitch(defaultMaxMIDIPitch), + m_tuningFrequency(defaultTuningFrequency), + m_bpo(defaultBPO), + m_interpolation(CQSpectrogram::InterpolateLinear), + m_cq(0), + m_maxFrequency(defaultMaxFrequency), + m_minFrequency(defaultMinFrequency), + m_haveStartTime(false), + m_columnCount(0) +{ +} + +CQVamp::~CQVamp() +{ + delete m_cq; +} + +string +CQVamp::getIdentifier() const +{ + if (m_midiPitchParameters) { + return "cqvampmidi"; + } else { + return "cqvamp"; + } +} + +string +CQVamp::getName() const +{ + if (m_midiPitchParameters) { + return "CQ Constant-Q Spectrogram (MIDI pitch range)"; + } else { + return "CQ Constant-Q Spectrogram (Hz range)"; + } +} + +string +CQVamp::getDescription() const +{ + if (m_midiPitchParameters) { + return "Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in MIDI pitch units."; + } else { + return "Extract a spectrogram with constant ratio of centre frequency to resolution from the input audio, specifying the frequency range in Hz."; + } +} + +string +CQVamp::getMaker() const +{ + return "Queen Mary, University of London"; +} + +int +CQVamp::getPluginVersion() const +{ + return 2; +} + +string +CQVamp::getCopyright() const +{ + return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2015 QMUL. BSD/MIT licence."; +} + +CQVamp::ParameterList +CQVamp::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor desc; + + if (m_midiPitchParameters) { + + desc.identifier = "minpitch"; + desc.name = "Minimum Pitch"; + desc.unit = "MIDI units"; + desc.description = "MIDI pitch corresponding to the lowest frequency to be included in the constant-Q transform. (The actual minimum frequency may be lower, as the range always covers an integral number of octaves below the highest frequency.)"; + desc.minValue = 0; + desc.maxValue = 127; + desc.defaultValue = defaultMinMIDIPitch; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "maxpitch"; + desc.name = "Maximum Pitch"; + desc.unit = "MIDI units"; + desc.description = "MIDI pitch corresponding to the highest frequency to be included in the constant-Q transform"; + desc.minValue = 0; + desc.maxValue = 127; + desc.defaultValue = defaultMaxMIDIPitch; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "tuning"; + desc.name = "Tuning Frequency"; + desc.unit = "Hz"; + desc.description = "Frequency of concert A"; + desc.minValue = 360; + desc.maxValue = 500; + desc.defaultValue = defaultTuningFrequency; + desc.isQuantized = false; + list.push_back(desc); + + } else { + + desc.identifier = "minfreq"; + desc.name = "Minimum Frequency"; + desc.unit = "Hz"; + desc.description = "Lowest frequency to be included in the constant-Q transform. (The actual minimum frequency may be lower, as the range always covers an integral number of octaves below the highest frequency.)"; + desc.minValue = 1; + desc.maxValue = 22050; + desc.defaultValue = defaultMinFrequency; + desc.isQuantized = false; + list.push_back(desc); + + desc.identifier = "maxfreq"; + desc.name = "Maximum Frequency"; + desc.unit = "Hz"; + desc.description = "MIDI pitch corresponding to the highest frequency to be included in the constant-Q transform"; + desc.minValue = 1; + desc.maxValue = 22050; + desc.defaultValue = defaultMaxFrequency; + desc.isQuantized = false; + list.push_back(desc); + } + + desc.identifier = "bpo"; + desc.name = "Bins per Octave"; + desc.unit = "bins"; + desc.description = "Number of constant-Q transform bins per octave"; + desc.minValue = 2; + desc.maxValue = 480; + desc.defaultValue = defaultBPO; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + desc.identifier = "interpolation"; + desc.name = "Interpolation"; + desc.unit = ""; + desc.description = "Interpolation method used to fill empty cells in lower octaves"; + desc.minValue = 0; + desc.maxValue = 2; + desc.defaultValue = 2; + desc.isQuantized = true; + desc.quantizeStep = 1; + desc.valueNames.push_back("None, leave as zero"); + desc.valueNames.push_back("None, repeat prior value"); + desc.valueNames.push_back("Linear interpolation"); + list.push_back(desc); + + return list; +} + +float +CQVamp::getParameter(std::string param) const +{ + if (param == "minpitch" && m_midiPitchParameters) { + return m_minMIDIPitch; + } + if (param == "maxpitch" && m_midiPitchParameters) { + return m_maxMIDIPitch; + } + if (param == "tuning" && m_midiPitchParameters) { + return m_tuningFrequency; + } + if (param == "bpo") { + return m_bpo; + } + if (param == "interpolation") { + return (float)m_interpolation; + } + if (param == "minfreq" && !m_midiPitchParameters) { + return m_minFrequency; + } + if (param == "maxfreq" && !m_midiPitchParameters) { + return m_maxFrequency; + } + std::cerr << "WARNING: CQVamp::getParameter: unknown parameter \"" + << param << "\"" << std::endl; + return 0.0; +} + +void +CQVamp::setParameter(std::string param, float value) +{ + if (param == "minpitch" && m_midiPitchParameters) { + m_minMIDIPitch = int(value + 0.5f); + } else if (param == "maxpitch" && m_midiPitchParameters) { + m_maxMIDIPitch = int(value + 0.5f); + } else if (param == "tuning" && m_midiPitchParameters) { + m_tuningFrequency = value; + } else if (param == "bpo") { + m_bpo = int(value + 0.5f); + } else if (param == "interpolation") { + m_interpolation = (CQSpectrogram::Interpolation)int(value + 0.5f); + } else if (param == "minfreq" && !m_midiPitchParameters) { + m_minFrequency = value; + } else if (param == "maxfreq" && !m_midiPitchParameters) { + m_maxFrequency = value; + } else { + std::cerr << "WARNING: CQVamp::setParameter: unknown parameter \"" + << param << "\"" << std::endl; + } +} + +bool +CQVamp::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (m_cq) { + delete m_cq; + m_cq = 0; + } + + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = stepSize; + m_blockSize = blockSize; + + if (m_midiPitchParameters) { + m_minFrequency = Pitch::getFrequencyForPitch + (m_minMIDIPitch, 0, m_tuningFrequency); + m_maxFrequency = Pitch::getFrequencyForPitch + (m_maxMIDIPitch, 0, m_tuningFrequency); + } + + reset(); + + if (!m_cq || !m_cq->isValid()) { + cerr << "CQVamp::initialise: Constant-Q parameters not valid! Not initialising" << endl; + return false; + } + + return true; +} + +void +CQVamp::reset() +{ + delete m_cq; + CQParameters p(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo); + m_cq = new CQSpectrogram(p, m_interpolation); + m_haveStartTime = false; + m_columnCount = 0; +} + +size_t +CQVamp::getPreferredStepSize() const +{ + return 0; +} + +size_t +CQVamp::getPreferredBlockSize() const +{ + return 0; +} + +std::string +CQVamp::noteName(int i) const +{ + static const char *names[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + + const char *n = names[i % 12]; + int oct = i / 12 - 1; + char buf[20]; + sprintf(buf, "%d %s%d", i, n, oct); + + return buf; +} + +CQVamp::OutputList +CQVamp::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "constantq"; + d.name = "Constant-Q Spectrogram"; + d.unit = ""; + d.description = "Output of constant-Q transform, as a single vector per process block"; + d.hasFixedBinCount = true; + d.binCount = (m_cq ? m_cq->getTotalBins() : (9 * 24)); + + if (m_cq) { + char name[20]; + for (int i = 0; i < (int)d.binCount; ++i) { + float freq = m_cq->getBinFrequency(d.binCount - i - 1); + sprintf(name, "%.1f Hz", freq); + int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency); + float nearestFreq = + Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency); + if (fabs(freq - nearestFreq) < 0.01) { + d.binNames.push_back(name + std::string(" ") + noteName(note)); + } else { + d.binNames.push_back(name); + } + } + } + + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256); + list.push_back(d); + + return list; +} + +CQVamp::FeatureSet +CQVamp::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (!m_cq) { + cerr << "ERROR: CQVamp::process: " + << "Plugin has not been initialised" + << endl; + return FeatureSet(); + } + + if (!m_haveStartTime) { + m_startTime = timestamp; + m_haveStartTime = true; + } + + vector<double> data; + for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]); + + vector<vector<double> > cqout = m_cq->process(data); + return convertToFeatures(cqout); +} + +CQVamp::FeatureSet +CQVamp::getRemainingFeatures() +{ + vector<vector<double> > cqout = m_cq->getRemainingOutput(); + return convertToFeatures(cqout); +} + +CQVamp::FeatureSet +CQVamp::convertToFeatures(const vector<vector<double> > &cqout) +{ + FeatureSet returnFeatures; + + int width = cqout.size(); + int height = m_cq->getTotalBins(); + + for (int i = 0; i < width; ++i) { + + vector<float> column(height, 0.f); + int thisHeight = cqout[i].size(); + for (int j = 0; j < thisHeight; ++j) { + column[j] = cqout[i][j]; + } + + // put low frequencies at the start + std::reverse(column.begin(), column.end()); + + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime + (m_columnCount * m_cq->getColumnHop() - m_cq->getLatency(), + m_inputSampleRate); + feature.values = column; + feature.label = ""; + +// cerr << "timestamp = " << feature.timestamp << " (start time = " << m_startTime << ", column count = " << m_columnCount << ", latency = " << m_cq->getLatency() << ", sample rate " << m_inputSampleRate << ")" << endl; + + if (feature.timestamp >= m_startTime) { + returnFeatures[0].push_back(feature); + } + + ++m_columnCount; + } + + return returnFeatures; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/vamp/CQVamp.h Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef CQVAMP_H +#define CQVAMP_H + +#include <vamp-sdk/Plugin.h> + +#include "cq/CQSpectrogram.h" + +class ConstantQ; + +class CQVamp : public Vamp::Plugin +{ +public: + CQVamp(float inputSampleRate, bool midiPitchParameters); + virtual ~CQVamp(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + bool m_midiPitchParameters; + int m_minMIDIPitch; + int m_maxMIDIPitch; + float m_tuningFrequency; + int m_bpo; + CQSpectrogram::Interpolation m_interpolation; + + CQSpectrogram *m_cq; + float m_maxFrequency; + float m_minFrequency; + int m_stepSize; + int m_blockSize; + + Vamp::RealTime m_startTime; + bool m_haveStartTime; + int m_columnCount; + + std::string noteName(int i) const; + + std::vector<float> m_prevFeature; + FeatureSet convertToFeatures(const std::vector<std::vector<double> > &); +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/constant-q-cpp/vamp/libmain.cpp Thu May 19 13:54:23 2016 +0100 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Constant-Q library + Copyright (c) 2013-2014 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include <vamp/vamp.h> +#include <vamp-sdk/PluginAdapter.h> + +#include "CQVamp.h" +#include "CQChromaVamp.h" + +class CQVampPluginAdapter : public Vamp::PluginAdapterBase +{ +public: + CQVampPluginAdapter(bool midiPitchParameters) : + PluginAdapterBase(), + m_midiPitchParameters(midiPitchParameters) + { } + + virtual ~CQVampPluginAdapter() { } + +protected: + bool m_midiPitchParameters; + + Vamp::Plugin *createPlugin(float inputSampleRate) { + return new CQVamp(inputSampleRate, m_midiPitchParameters); + } +}; + +static CQVampPluginAdapter midiAdapter(true); +static CQVampPluginAdapter hzAdapter(false); +static Vamp::PluginAdapter<CQChromaVamp> chromaAdapter; + +const VampPluginDescriptor * +vampGetPluginDescriptor(unsigned int version, unsigned int index) +{ + if (version < 1) return 0; + + switch (index) { + case 0: return hzAdapter.getDescriptor(); + case 1: return midiAdapter.getDescriptor(); + case 2: return chromaAdapter.getDescriptor(); + default: return 0; + } +} + +