Import initial work

This commit is contained in:
Laurence Withers 2010-11-08 15:18:07 +00:00
commit ce36ca9ef5
46 changed files with 5070 additions and 0 deletions

1
src/docs/.params Normal file
View file

@ -0,0 +1 @@
docs doxygen docs

207
src/docs/Doxyfile.in Normal file
View file

@ -0,0 +1,207 @@
# libiir/src/docs/Doxyfile.in
#
# Copyright: ©2010, Laurence Withers.
# Author: Laurence Withers <l@lwithers.me.uk>
# License: GPLv3
#
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = libiir
OUTPUT_DIRECTORY =
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF =
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = YES
FULL_PATH_NAMES = NO
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = YES
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 4
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
BUILTIN_STL_SUPPORT = NO
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = NO
DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
TYPEDEF_HIDES_STRUCT = NO
SYMBOL_CACHE_SIZE = 0
EXTRACT_ALL = NO
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = NO
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = YES
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = NO
SORT_BRIEF_DOCS = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = NO
SHOW_DIRECTORIES = NO
SHOW_FILES = NO
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
QUIET = YES
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
INPUT =
INPUT_ENCODING = UTF-8
FILE_PATTERNS =
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH = src/docs
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
USE_HTAGS = NO
VERBATIM_HEADERS = NO
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_ALIGN_MEMBERS = YES
HTML_DYNAMIC_SECTIONS = YES
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_BUNDLE_ID = org.doxygen.Project
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QCH_FILE =
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
QHG_LOCATION =
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = NO
TREEVIEW_WIDTH = 250
FORMULA_FONTSIZE = 10
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = NO
USE_PDFLATEX = NO
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
XML_PROGRAMLISTING = YES
GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED = DOXYGEN \
__attribute__()=
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES
MSCGEN_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
DOT_FONTNAME = FreeSans
DOT_FONTSIZE = 10
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = NO
UML_LOOK = NO
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = NO
INCLUDED_BY_GRAPH = NO
CALL_GRAPH = NO
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = NO
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = YES
DOT_MULTI_TARGETS = YES
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
SEARCHENGINE = NO

55
src/docs/MainPage.dox Normal file
View file

@ -0,0 +1,55 @@
/* libiir/src/docs/MainPage.dox
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \mainpage
This library allows the implementation of arbitrary IIR filters in C. It has
functions for generating and manipulating filters in terms of coefficients, for
chaining arbitrary filters together, and for generating coefficients for some
common types of filter. See \ref iir_structure for a definition of the IIR
filter equation.
\section creation Filter creation
At a high level, filters may be specified as strings. See \ref string_desc for
the required format and \ref iir_parse() for a C function returning a filter
instance from such a string.
Otherwise, the library user must first create a set of coefficients using
\ref iir_coeff_new(). Any number of filters can then be instantiated using that
set of coefficients with \ref iir_filter_new(), or the coefficients can be
chained on to the end of an existing filter instance with
\ref iir_filter_chain(). See \ref common_filters for functions to generate
coefficients.
\section operation Filter operation
The function \ref iir_filter() will actually process an input sample through the
coefficient chain and produce the output sample. Effectively it produces
<code>y(t)</code> given <code>x(t)</code>.
A filter may be copied, possibly including its state (for initial conditions),
using the function \ref iir_filter_copy().
\section tools Tools
In the <code>tests</code> directory are some simple tools for examining and
experimenting with filters. <code>run_filter</code> takes a stream of input
samples <code>x(t)</code> and produces the filtered output samples
<code>y(t)</code>.
Perhaps more interesting is <code>plot_filter</code> (requires GNUplot to be
installed) which will generate a Bode plot for a given filter chain. Note the
phase response can be a little rough due to the simplistic time-domain analysis
of the output signal's phase.
*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=doxygen
*/

1
src/docs/build.default Normal file
View file

@ -0,0 +1 @@
source src/docs/build.docs

43
src/docs/build.docs Normal file
View file

@ -0,0 +1,43 @@
# These are external variables, and shouldn't clash with anything else
# docs_BUILT
#
MONOLITHIC_DOC="${MONOLITHIC_DOC} $(echo src/docs/*.dox)"
build_target monolithic
if [ -z ${docs_BUILT} ]
then
echo "Building documentation with Doxygen..."
DOXYFILE=obj/Doxyfile.docs
if [ ! -e ${DOXYFILE} ]
then
do_cmd cp src/docs/Doxyfile.in ${DOXYFILE} || return 1
echo "INPUT = ${MONOLITHIC_DOC}" >> ${DOXYFILE}
echo "PROJECT_NUMBER = ${VERSION}" >> ${DOXYFILE}
fi
MODIFIED=0
for file in ${MONOLITHIC_DOC}
do
if [ ${file} -nt html/index.html ]
then
MODIFIED=1
break
fi
done
if [ ${MODIFIED} -ne 0 ]
then
do_cmd doxygen ${DOXYFILE} || return 1
print_success "Documentation built"
else
print_success "Documentation is up to date"
fi
docs_BUILT=1
fi
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

1
src/docs/build.install Normal file
View file

@ -0,0 +1 @@
source src/docs/build.install-docs

View file

@ -0,0 +1,21 @@
build_target docs
# create documentation directories
echo "Installing documentation into ${DOCSDIR}"
build_dir_tree "${DOCSDIR}/html" || return 1
# copy across the Doxygen-generated documentation
for file in html/*
do
install_file ${file} ${DOCSDIR}/html 0644 || return 1
done
# copy across the generic files
for file in COPYING README
do
install_file ${file} ${DOCSDIR} 0644 || return 1
done
print_success "Documentation installed"
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

View file

@ -0,0 +1,29 @@
/* libiir/src/docs/iir_structure.dox
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \page iir_structure Structure of IIR filter
For the purposes of this library, the following notation is used:
\li <code>x(t)</code>: value of input function at time \a t (\a t = 0, 1, 2, …)
\li <code>y(t)</code>: value of output at time \a t
\li <code>c[n]</code>: array of \c x(t) coefficients
\li <code>d[n]</code>: array of \c y(t) coefficients
This leads to a general IIR filter equation:
<code>y(t) = x(t).c[0] + x(t-1).c[1] + … + x(t-N).c[N] - y(t-1).d[0] - y(t-2).d[1] - … - y(t-1-M).d[M]</code>
For initial conditions, the library sets <code>y(t)</code> for t &lt; 0 to 0,
and <code>x(t)</code> for t &lt; 0 to <code>x(0)</code>.
*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=doxygen
*/

59
src/docs/string_desc.dox Normal file
View file

@ -0,0 +1,59 @@
/* libiir/src/docs/string_desc.dox
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \page string_desc Describing IIR filters as strings
This library allows the user to describe an IIR filter chain as a string, which
is useful to allow configurable filtering using e.g. a configuration file. This
page describes the format of such strings.
The string is first split into individual IIR filters. Each filter is written as
\c type(params) and separated by whitespace. The string must contain at least
one filter but may contain an arbitrary number.
\section string_desc_types Description of filter types
\subsection string_desc_coeff Raw coefficients
An IIR filter may be specified as raw coefficients, in which case the \c type
is \c raw and the \c params consists of a string:
<code>c[0],c[1],…,c[n]/d[0],d[1]…d[n]</code>
The coefficients <code>c[0]…c[i]</code> and <code>d[0]…d[i]</code> are written
in standard C floating-point notation. They are separated by commas, except the
transition between \c c and \c d coefficients, which is separated by a slash.
\subsection string_desc_bwlp Butterworth filters
For a low-pass filter, type is \c butterworth_lowpass. For a high-pass filter,
type is \c butterworth_highpass. Parameters as per
\ref iir_butterworth_lowpass(), i.e.:
<code>order,gain,corner</code>
For band-pass filters, type is \c butterworth_bandpass. For band-stop filters,
type is \c butterworth_bandstop. Parameters as per
\ref iir_butterworth_bandpass(), i.e.:
<code>order,gain,low_corner,high_corner</code>
Anything greater than 4th order will be split into multiple 4th-order (or less)
segments. Note however this will lead to the corner frequencies being off.
*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=doxygen
*/

1
src/libiir/.params Normal file
View file

@ -0,0 +1 @@
lib c libiir iir.h

View file

@ -0,0 +1,16 @@
/* libiir/src/libiir/000_TopHeader.h
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#ifndef HEADER_libiir
#define HEADER_libiir
/* standard includes, or includes needed for type declarations */
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

View file

@ -0,0 +1,20 @@
/* libiir/src/libiir/000_TopSource.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#include "iir.h"
/* Below are all the includes used throughout the library. */
#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

248
src/libiir/200_iir.c Normal file
View file

@ -0,0 +1,248 @@
/* libiir/src/libiir/200_iir.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/* struct iir_coeff_t
* Holds a general IIR filter (i.e. the set of coefficients that define it).
* nc >= 1 and nd >= 1.
*/
struct iir_coeff_t {
int nc, nd;
double* c, * d;
};
/* iir_coeff_new()
* Allocates a new set of coefficient objects.
*/
struct iir_coeff_t*
iir_coeff_new(int nc, double* c, int nd, double* d)
{
struct iir_coeff_t* coeff;
if(nc < 1 || nd < 1) {
errno = EINVAL;
return 0;
}
coeff = malloc(sizeof(struct iir_coeff_t));
coeff->nc = nc;
coeff->nd = nd;
coeff->c = malloc(sizeof(double) * nc);
coeff->d = malloc(sizeof(double) * nd);
memcpy(coeff->c, c, sizeof(double) * nc);
memcpy(coeff->d, d, sizeof(double) * nd);
return coeff;
}
/* iir_coeff_free()
* Frees memory associated with coeff.
*/
void
iir_coeff_free(struct iir_coeff_t* coeff)
{
if(!coeff) return;
free(coeff->c);
free(coeff->d);
free(coeff);
}
/* struct iir_filter_t
* An instantiated IIR filter. This is actually a linked list node, so that we
* can create chains of filters. It also has a copy of the coefficients so that
* the library user doesn't need to keep the struct iir_coeff_t instances
* around.
*/
struct iir_filter_t {
/* pointer to next stage */
struct iir_filter_t* next;
/* coefficients for this stage */
int nc, nd;
double* c, * d;
/* state for this stage */
int ready; /* if clear, first sample is used to set initial conditions */
double* x, * y;
int xpos, ypos;
};
/* iir_filter_new()
* Allocates a new IIR filter instance object, copying the coefficients out of
* coeff.
*/
struct iir_filter_t*
iir_filter_new(const struct iir_coeff_t* coeff)
{
struct iir_filter_t* fi;
fi = malloc(sizeof(struct iir_filter_t));
fi->next = 0;
fi->ready = fi->xpos = fi->ypos = 0;
/* copy in the coefficients */
fi->nc = coeff->nc;
fi->nd = coeff->nd;
fi->c = malloc(sizeof(double) * fi->nc);
fi->d = malloc(sizeof(double) * fi->nd);
memcpy(fi->c, coeff->c, sizeof(double) * fi->nc);
memcpy(fi->d, coeff->d, sizeof(double) * fi->nd);
/* allocate space for state */
fi->x = malloc(sizeof(double) * fi->nc);
fi->y = malloc(sizeof(double) * fi->nd);
memset(fi->y, 0, sizeof(double) * fi->nd);
return fi;
}
/* iir_filter_free()
* Frees a filter chain.
*/
void
iir_filter_free(struct iir_filter_t* fi)
{
struct iir_filter_t* next;
while(fi) {
next = fi->next;
free(fi->c);
free(fi->d);
free(fi->x);
free(fi->y);
free(fi);
fi = next;
}
}
/* iir_filter_chain()
* Extends an IIR filter instance with another filter.
*/
void
iir_filter_chain(struct iir_filter_t* fi, const struct iir_coeff_t* coeff)
{
/* go to end of linked list */
while(fi->next) fi = fi->next;
/* add to end of chain */
fi->next = iir_filter_new(coeff);
}
/* iir_filter_copy()
* Performs a deep copy of a filter instance chain.
*/
struct iir_filter_t* iir_filter_copy(const struct iir_filter_t* fi, int state)
{
struct iir_filter_t* head = 0, * tail = 0, * copy;
while(fi) {
copy = malloc(sizeof(struct iir_filter_t));
copy->next = 0;
copy->nc = fi->nc;
copy->nd = fi->nd;
copy->c = malloc(sizeof(double) * fi->nc);
copy->x = malloc(sizeof(double) * fi->nc);
copy->d = malloc(sizeof(double) * fi->nd);
copy->y = malloc(sizeof(double) * fi->nd);
memcpy(copy->c, fi->c, sizeof(double) * fi->nc);
memcpy(copy->d, fi->d, sizeof(double) * fi->nd);
if(state) {
copy->ready = 1;
memcpy(copy->x, fi->x, sizeof(double) * fi->nc);
memcpy(copy->y, fi->y, sizeof(double) * fi->nd);
copy->xpos = fi->xpos;
copy->ypos = fi->ypos;
} else {
memset(copy->y, 0, sizeof(double) * fi->nd);
copy->ready = copy->xpos = copy->ypos = 0;
}
if(!head) {
head = tail = copy;
} else {
tail->next = copy;
tail = copy;
}
fi = fi->next;
}
return head;
}
/* iir_filter()
* Processes a sample, possibly dispatching it down the chain.
*/
static double
iir_get_xy(const double* xy, int pos, int max, int step)
{
pos -= step + 1;
if(pos < 0) pos += max;
return xy[pos];
}
double
iir_filter(struct iir_filter_t* fi, double samp)
{
int i;
while(fi) {
if(!fi->ready) {
/* initial conditions */
for(i = 0; i < fi->nc; ++i) fi->x[i] = samp;
fi->ready = 1;
}
/* update input array with sample x(t) */
fi->x[fi->xpos] = samp;
if(++fi->xpos == fi->nc) fi->xpos = 0;
samp = 0;
/* sum of c[i].x(t-i) */
for(i = 0; i < fi->nc; ++i) {
samp += fi->c[i] * iir_get_xy(fi->x, fi->xpos, fi->nc, i);
}
/* sum of d[i].y(t-i-1) */
for(i = 0; i < fi->nd; ++i) {
samp -= fi->d[i] * iir_get_xy(fi->y, fi->ypos, fi->nd, i);
}
/* update output array with new result y(t) */
fi->y[fi->ypos] = samp;
if(++fi->ypos == fi->nd) fi->ypos = 0;
fi = fi->next;
}
return samp;
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

169
src/libiir/200_iir.h Normal file
View file

@ -0,0 +1,169 @@
/* libiir/src/libiir/200_iir.h
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \defgroup iir Basic IIR filtering
The functions in this module present a basic interface for representing IIR
filters, creating instances of (possibly chained) IIR filters, and filtering an
input sample.
A general IIR filter consists of a set of coefficients, and may be created
through \ref iir_coeff_new(). The filter object (the opaque <code>struct
iir_coeff_t</code>) is then used to instantiate specific filters (the opaque
<code>struct iir_filter_t</code>) through \ref iir_filter_new(). Each filter
instance may have an arbitrary further number of IIR filters chained on to it
through \ref iir_filter_chain(). The filter processes one sample at a time
through \ref iir_filter().
*/
/*!@{*/
/* opaque structure */
struct iir_coeff_t;
/*! \brief Create general IIR filter
\param nc Number of \a c coefficients.
\param c Array of \a c coefficients.
\param nd Number of \a d coefficients.
\param d Array of \a d coefficients.
\returns Pointer to new general IIR filter object.
This function creates a new general IIR filter object which may be used to
create filter instances through \ref iir_filter_new() or chained on to existing
instances through \ref iir_filter().
See \ref iir_structure for a full explanation of the parameters.
*/
struct iir_coeff_t* iir_coeff_new(int nc, double* c, int nd, double* d)
#ifndef DOXYGEN
__attribute__((malloc,nonnull))
#endif
;
/*! \brief Free general IIR filter
\param coeff Pointer to IIR filter object. May be 0.
Frees a set of IIR filter coefficients previously allocated through
\ref iir_coeff_new(). Can be called on a null pointer without consequences.
Note that \ref iir_filter_new() and \ref iir_filter_chain() actually store a
copy of the coefficients, so it is possible to free \a coeff even if existing
filters are still using its coefficient values.
*/
void iir_coeff_free(struct iir_coeff_t* coeff);
/* opaque structure */
struct iir_filter_t;
/*! \brief Create IIR filter instance
\param coeff Filter coefficients to use.
\returns Pointer to new instance of IIR filter.
Creates a new instance of a general IIR filter. The set of coefficients \a coeff
is copied into the returned structure, meaning the coefficients can be freed
after this function returns if they will not be needed again.
The first sample passed through \ref iir_filter() will be used to set initial
conditions.
An arbitrary number of further filters may be chained on to the end of this
instance through \ref iir_filter_chain().
*/
struct iir_filter_t* iir_filter_new(const struct iir_coeff_t* coeff)
#ifndef DOXYGEN
__attribute__((malloc,nonnull))
#endif
;
/*! \brief Free IIR filter instance
\param fi Filter object to free. May be 0.
Frees a previously-allocated IIR filter instance. Can be called on a null
pointer without consequences.
*/
void iir_filter_free(struct iir_filter_t* fi);
/*! \brief Add a further IIR filter to a filter instance
\param fi Filter instance to chain onto.
\param coeff New IIR filter coefficients to add to chain.
Extends an existing IIR filter by chaining a new set of coefficients onto the
end. This can be used for &gt;4th order Butterworth filters, for example. This
copies the set of coefficients from \a coeff so the coefficients can be freed
after this function returns if they are no longer required.
*/
void iir_filter_chain(struct iir_filter_t* fi, const struct iir_coeff_t* coeff)
#ifndef DOXYGEN
__attribute__((nonnull))
#endif
;
/*! \brief Create a deep copy of an IIR filter instance
\param fi Filter instance to copy.
\param state Non-zero to copy state as well.
\returns Pointer to newly-allocated filter instance.
Performs a deep copy of the filter instance \a fi. If \a state is non-zero,
then the internal state of \a fi is copied as well (otherwise it is as treated
as a brand new instance).
*/
struct iir_filter_t* iir_filter_copy(const struct iir_filter_t* fi, int state)
#ifndef DOXYGEN
__attribute__((malloc,nonnull))
#endif
;
/*! \brief Process a sample
\param fi Filter object to run.
\param samp Input sample \c x(t).
\returns Filtered output sample \c y(t).
Given the input sample \c x(t) (the parameter \a samp), runs the filter chain in
\a fi and produces the output sample \c y(t), which it returns.
*/
double iir_filter(struct iir_filter_t* fi, double samp);
/*!@}*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

View file

@ -0,0 +1,161 @@
/* libiir/src/libiir/300_common_filters.h
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \defgroup common_filters Common types of IIR filter
Functions to create coefficients for various common types of IIR filter. The
coefficient structures which are returned may be used to instantiate IIR
filters using \ref iir_filter_new().
The Butterworth filter code comes from the Exstrom Labs LLC code available under
GPLv2 or later and published at http://www.exstrom.com/journal/sigproc/ . There
is a copy of the original code available in the top level of this project.
*/
/*!@{*/
/*! \brief nth-order Butterworth low-pass
\param order Order of filter (1).
\param gain Linear gain of filter.
\param corner Corner frequency expressed as a fraction of Nyquist
(0 \a corner 1)
\returns Newly-allocated IIR filter coefficients.
Uses the Exstrom labs code to compute the coefficients of an nth-order (param
\a order) Butterworth-type low pass filter with gain \a gain and corner
frequency \a corner.
Note it is recommended to chain multiple filters together to build anything
greater than a 4th-order filter. This function won't do that directly for you.
\a gain will usually be set to be 1.0.
The corner frequency \a corner is expressed as a fraction of the sampling
frequency (which is of course not known by the IIR code). It should lie between
0 (0Hz) and 1 (the Nyquist frequency, or ½ the sampling frequency).
*/
struct iir_coeff_t* iir_butterworth_lowpass(int order,
double gain,
double corner)
#ifndef DOXYGEN
__attribute__((nonnull))
#endif
;
/*! \brief nth-order Butterworth high-pass
\param order Order of filter (1).
\param gain Linear gain of filter.
\param corner Corner frequency expressed as a fraction of Nyquist
(0 \a corner 1)
\returns Newly-allocated IIR filter coefficients.
Uses the Exstrom labs code to compute the coefficients of an nth-order (param
\a order) Butterworth-type high pass filter with gain \a gain and corner
frequency \a corner.
Note it is recommended to chain multiple filters together to build anything
greater than a 4th-order filter. This function won't do that directly for you.
\a gain will usually be set to be 1.0.
The corner frequency \a corner is expressed as a fraction of the sampling
frequency (which is of course not known by the IIR code). It should lie between
0 (0Hz) and 1 (the Nyquist frequency, or ½ the sampling frequency).
*/
struct iir_coeff_t* iir_butterworth_highpass(int order,
double gain,
double corner)
#ifndef DOXYGEN
__attribute__((nonnull))
#endif
;
/*! \brief nth-order Butterworth band-pass
\param order Order of filter (1).
\param gain Linear gain of filter.
\param c1 Low corner frequency expressed as a fraction of Nyquist
(0 \a c1 1)
\param c2 High corner frequency expressed as a fraction of Nyquist
(0 \a c2 1, and \a c1 &lt; \a c2)
\returns Newly-allocated IIR filter coefficients.
Uses the Exstrom labs code to compute the coefficients of an nth-order (param
\a order) Butterworth-type band pass filter with gain \a gain and corner
frequencies \a c1 and \a c2.
Note it is recommended to chain multiple filters together to build anything
greater than a 4th-order filter. This function won't do that directly for you.
\a gain will usually be set to be 1.0.
The corner frequencies \a c1 and \a c2 are expressed as a fraction of the
sampling frequency (which is of course not known by the IIR code). They should
lie between 0 (0Hz) and 1 (the Nyquist frequency, or ½ the sampling frequency),
and \a c2 should be greater than \a c1.
*/
struct iir_coeff_t* iir_butterworth_bandpass(int order,
double gain,
double c1,
double c2)
#ifndef DOXYGEN
__attribute__((nonnull))
#endif
;
/*! \brief nth-order Butterworth band-stop
\param order Order of filter (1).
\param gain Linear gain of filter.
\param c1 Low corner frequency expressed as a fraction of Nyquist
(0 \a c1 1)
\param c2 High corner frequency expressed as a fraction of Nyquist
(0 \a c2 1, and \a c1 &lt; \a c2)
\returns Newly-allocated IIR filter coefficients.
Uses the Exstrom labs code to compute the coefficients of an nth-order (param
\a order) Butterworth-type band stop filter with gain \a gain and corner
frequencies \a c1 and \a c2.
Note it is recommended to chain multiple filters together to build anything
greater than a 4th-order filter. This function won't do that directly for you.
\a gain will usually be set to be 1.0.
The corner frequencies \a c1 and \a c2 are expressed as a fraction of the
sampling frequency (which is of course not known by the IIR code). They should
lie between 0 (0Hz) and 1 (the Nyquist frequency, or ½ the sampling frequency),
and \a c2 should be greater than \a c1.
*/
struct iir_coeff_t* iir_butterworth_bandstop(int order,
double gain,
double c1,
double c2)
#ifndef DOXYGEN
__attribute__((nonnull))
#endif
;
/*!@}*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

View file

@ -0,0 +1,600 @@
/* Modifications for libiir:
* · added static qualifiers to all functions
* · removed #include lines, they are covered by 000_TopSource.c
*
* These changes are:
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*
* COPYRIGHT
*
* liir - Recursive digital filter functions
* Copyright (C) 2007 Exstrom Laboratories LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* A copy of the GNU General Public License is available on the internet at:
*
* http://www.gnu.org/copyleft/gpl.html
*
* or you can write to:
*
* The Free Software Foundation, Inc.
* 675 Mass Ave
* Cambridge, MA 02139, USA
*
* You can contact Exstrom Laboratories LLC via Email at:
*
* stefan(AT)exstrom.com
*
* or you can write to:
*
* Exstrom Laboratories LLC
* P.O. Box 7651
* Longmont, CO 80501, USA
*
*/
/**********************************************************************
binomial_mult - multiplies a series of binomials together and returns
the coefficients of the resulting polynomial.
The multiplication has the following form:
(x+p[0])*(x+p[1])*...*(x+p[n-1])
The p[i] coefficients are assumed to be complex and are passed to the
function as a pointer to an array of doubles of length 2n.
The resulting polynomial has the following form:
x^n + a[0]*x^n-1 + a[1]*x^n-2 + ... +a[n-2]*x + a[n-1]
The a[i] coefficients can in general be complex but should in most
cases turn out to be real. The a[i] coefficients are returned by the
function as a pointer to an array of doubles of length 2n. Storage
for the array is allocated by the function and should be freed by the
calling program when no longer needed.
Function arguments:
n - The number of binomials to multiply
p - Pointer to an array of doubles where p[2i] (i=0...n-1) is
assumed to be the real part of the coefficient of the ith binomial
and p[2i+1] is assumed to be the imaginary part. The overall size
of the array is then 2n.
*/
static double *
binomial_mult( int n, double *p )
{
int i, j;
double *a;
a = (double *)calloc( 2 * n, sizeof(double) );
if( a == NULL ) return( NULL );
for( i = 0; i < n; ++i )
{
for( j = i; j > 0; --j )
{
a[2*j] += p[2*i] * a[2*(j-1)] - p[2*i+1] * a[2*(j-1)+1];
a[2*j+1] += p[2*i] * a[2*(j-1)+1] + p[2*i+1] * a[2*(j-1)];
}
a[0] += p[2*i];
a[1] += p[2*i+1];
}
return( a );
}
/**********************************************************************
trinomial_mult - multiplies a series of trinomials together and returns
the coefficients of the resulting polynomial.
The multiplication has the following form:
(x^2 + b[0]x + c[0])*(x^2 + b[1]x + c[1])*...*(x^2 + b[n-1]x + c[n-1])
The b[i] and c[i] coefficients are assumed to be complex and are passed
to the function as a pointers to arrays of doubles of length 2n. The real
part of the coefficients are stored in the even numbered elements of the
array and the imaginary parts are stored in the odd numbered elements.
The resulting polynomial has the following form:
x^2n + a[0]*x^2n-1 + a[1]*x^2n-2 + ... +a[2n-2]*x + a[2n-1]
The a[i] coefficients can in general be complex but should in most cases
turn out to be real. The a[i] coefficients are returned by the function as
a pointer to an array of doubles of length 4n. The real and imaginary
parts are stored, respectively, in the even and odd elements of the array.
Storage for the array is allocated by the function and should be freed by
the calling program when no longer needed.
Function arguments:
n - The number of trinomials to multiply
b - Pointer to an array of doubles of length 2n.
c - Pointer to an array of doubles of length 2n.
*/
static double *
trinomial_mult( int n, double *b, double *c )
{
int i, j;
double *a;
a = (double *)calloc( 4 * n, sizeof(double) );
if( a == NULL ) return( NULL );
a[2] = c[0];
a[3] = c[1];
a[0] = b[0];
a[1] = b[1];
for( i = 1; i < n; ++i )
{
a[2*(2*i+1)] += c[2*i]*a[2*(2*i-1)] - c[2*i+1]*a[2*(2*i-1)+1];
a[2*(2*i+1)+1] += c[2*i]*a[2*(2*i-1)+1] + c[2*i+1]*a[2*(2*i-1)];
for( j = 2*i; j > 1; --j )
{
a[2*j] += b[2*i] * a[2*(j-1)] - b[2*i+1] * a[2*(j-1)+1] +
c[2*i] * a[2*(j-2)] - c[2*i+1] * a[2*(j-2)+1];
a[2*j+1] += b[2*i] * a[2*(j-1)+1] + b[2*i+1] * a[2*(j-1)] +
c[2*i] * a[2*(j-2)+1] + c[2*i+1] * a[2*(j-2)];
}
a[2] += b[2*i] * a[0] - b[2*i+1] * a[1] + c[2*i];
a[3] += b[2*i] * a[1] + b[2*i+1] * a[0] + c[2*i+1];
a[0] += b[2*i];
a[1] += b[2*i+1];
}
return( a );
}
/**********************************************************************
dcof_bwlp - calculates the d coefficients for a butterworth lowpass
filter. The coefficients are returned as an array of doubles.
*/
static double *
dcof_bwlp( int n, double fcf )
{
int k; // loop variables
double theta; // M_PI * fcf / 2.0
double st; // sine of theta
double ct; // cosine of theta
double parg; // pole angle
double sparg; // sine of the pole angle
double cparg; // cosine of the pole angle
double a; // workspace variable
double *rcof; // binomial coefficients
double *dcof; // dk coefficients
rcof = (double *)calloc( 2 * n, sizeof(double) );
if( rcof == NULL ) return( NULL );
theta = M_PI * fcf;
st = sin(theta);
ct = cos(theta);
for( k = 0; k < n; ++k )
{
parg = M_PI * (double)(2*k+1)/(double)(2*n);
sparg = sin(parg);
cparg = cos(parg);
a = 1.0 + st*sparg;
rcof[2*k] = -ct/a;
rcof[2*k+1] = -st*cparg/a;
}
dcof = binomial_mult( n, rcof );
free( rcof );
dcof[1] = dcof[0];
dcof[0] = 1.0;
for( k = 3; k <= n; ++k )
dcof[k] = dcof[2*k-2];
return( dcof );
}
/**********************************************************************
dcof_bwhp - calculates the d coefficients for a butterworth highpass
filter. The coefficients are returned as an array of doubles.
*/
static double *
dcof_bwhp( int n, double fcf )
{
return( dcof_bwlp( n, fcf ) );
}
/**********************************************************************
dcof_bwbp - calculates the d coefficients for a butterworth bandpass
filter. The coefficients are returned as an array of doubles.
*/
static double *
dcof_bwbp( int n, double f1f, double f2f )
{
int k; // loop variables
double theta; // M_PI * (f2f - f1f) / 2.0
double cp; // cosine of phi
double st; // sine of theta
double ct; // cosine of theta
double s2t; // sine of 2*theta
double c2t; // cosine 0f 2*theta
double *rcof; // z^-2 coefficients
double *tcof; // z^-1 coefficients
double *dcof; // dk coefficients
double parg; // pole angle
double sparg; // sine of pole angle
double cparg; // cosine of pole angle
double a; // workspace variables
cp = cos(M_PI * (f2f + f1f) / 2.0);
theta = M_PI * (f2f - f1f) / 2.0;
st = sin(theta);
ct = cos(theta);
s2t = 2.0*st*ct; // sine of 2*theta
c2t = 2.0*ct*ct - 1.0; // cosine of 2*theta
rcof = (double *)calloc( 2 * n, sizeof(double) );
tcof = (double *)calloc( 2 * n, sizeof(double) );
for( k = 0; k < n; ++k )
{
parg = M_PI * (double)(2*k+1)/(double)(2*n);
sparg = sin(parg);
cparg = cos(parg);
a = 1.0 + s2t*sparg;
rcof[2*k] = c2t/a;
rcof[2*k+1] = s2t*cparg/a;
tcof[2*k] = -2.0*cp*(ct+st*sparg)/a;
tcof[2*k+1] = -2.0*cp*st*cparg/a;
}
dcof = trinomial_mult( n, tcof, rcof );
free( tcof );
free( rcof );
dcof[1] = dcof[0];
dcof[0] = 1.0;
for( k = 3; k <= 2*n; ++k )
dcof[k] = dcof[2*k-2];
return( dcof );
}
/**********************************************************************
dcof_bwbs - calculates the d coefficients for a butterworth bandstop
filter. The coefficients are returned as an array of doubles.
*/
static double *
dcof_bwbs( int n, double f1f, double f2f )
{
int k; // loop variables
double theta; // M_PI * (f2f - f1f) / 2.0
double cp; // cosine of phi
double st; // sine of theta
double ct; // cosine of theta
double s2t; // sine of 2*theta
double c2t; // cosine 0f 2*theta
double *rcof; // z^-2 coefficients
double *tcof; // z^-1 coefficients
double *dcof; // dk coefficients
double parg; // pole angle
double sparg; // sine of pole angle
double cparg; // cosine of pole angle
double a; // workspace variables
cp = cos(M_PI * (f2f + f1f) / 2.0);
theta = M_PI * (f2f - f1f) / 2.0;
st = sin(theta);
ct = cos(theta);
s2t = 2.0*st*ct; // sine of 2*theta
c2t = 2.0*ct*ct - 1.0; // cosine 0f 2*theta
rcof = (double *)calloc( 2 * n, sizeof(double) );
tcof = (double *)calloc( 2 * n, sizeof(double) );
for( k = 0; k < n; ++k )
{
parg = M_PI * (double)(2*k+1)/(double)(2*n);
sparg = sin(parg);
cparg = cos(parg);
a = 1.0 + s2t*sparg;
rcof[2*k] = c2t/a;
rcof[2*k+1] = -s2t*cparg/a;
tcof[2*k] = -2.0*cp*(ct+st*sparg)/a;
tcof[2*k+1] = 2.0*cp*st*cparg/a;
}
dcof = trinomial_mult( n, tcof, rcof );
free( tcof );
free( rcof );
dcof[1] = dcof[0];
dcof[0] = 1.0;
for( k = 3; k <= 2*n; ++k )
dcof[k] = dcof[2*k-2];
return( dcof );
}
/**********************************************************************
ccof_bwlp - calculates the c coefficients for a butterworth lowpass
filter. The coefficients are returned as an array of integers.
*/
static int *
ccof_bwlp( int n )
{
int *ccof;
int m;
int i;
ccof = (int *)calloc( n+1, sizeof(int) );
if( ccof == NULL ) return( NULL );
ccof[0] = 1;
ccof[1] = n;
m = n/2;
for( i=2; i <= m; ++i)
{
ccof[i] = (n-i+1)*ccof[i-1]/i;
ccof[n-i]= ccof[i];
}
ccof[n-1] = n;
ccof[n] = 1;
return( ccof );
}
/**********************************************************************
ccof_bwhp - calculates the c coefficients for a butterworth highpass
filter. The coefficients are returned as an array of integers.
*/
static int *
ccof_bwhp( int n )
{
int *ccof;
int i;
ccof = ccof_bwlp( n );
if( ccof == NULL ) return( NULL );
for( i = 0; i <= n; ++i)
if( i % 2 ) ccof[i] = -ccof[i];
return( ccof );
}
/**********************************************************************
ccof_bwbp - calculates the c coefficients for a butterworth bandpass
filter. The coefficients are returned as an array of integers.
*/
static int *
ccof_bwbp( int n )
{
int *tcof;
int *ccof;
int i;
ccof = (int *)calloc( 2*n+1, sizeof(int) );
if( ccof == NULL ) return( NULL );
tcof = ccof_bwhp(n);
if( tcof == NULL ) return( NULL );
for( i = 0; i < n; ++i)
{
ccof[2*i] = tcof[i];
ccof[2*i+1] = 0.0;
}
ccof[2*n] = tcof[n];
free( tcof );
return( ccof );
}
/**********************************************************************
ccof_bwbs - calculates the c coefficients for a butterworth bandstop
filter. The coefficients are returned as an array of integers.
*/
static double *
ccof_bwbs( int n, double f1f, double f2f )
{
double alpha;
double *ccof;
int i, j;
alpha = -2.0 * cos(M_PI * (f2f + f1f) / 2.0) / cos(M_PI * (f2f - f1f) / 2.0);
ccof = (double *)calloc( 2*n+1, sizeof(double) );
ccof[0] = 1.0;
ccof[2] = 1.0;
ccof[1] = alpha;
for( i = 1; i < n; ++i )
{
ccof[2*i+2] += ccof[2*i];
for( j = 2*i; j > 1; --j )
ccof[j+1] += alpha * ccof[j] + ccof[j-1];
ccof[2] += alpha * ccof[1] + 1.0;
ccof[1] += alpha;
}
return( ccof );
}
/**********************************************************************
sf_bwlp - calculates the scaling factor for a butterworth lowpass filter.
The scaling factor is what the c coefficients must be multiplied by so
that the filter response has a maximum value of 1.
*/
static double
sf_bwlp( int n, double fcf )
{
int m, k; // loop variables
double omega; // M_PI * fcf
double fomega; // function of omega
double parg0; // zeroth pole angle
double sf; // scaling factor
omega = M_PI * fcf;
fomega = sin(omega);
parg0 = M_PI / (double)(2*n);
m = n / 2;
sf = 1.0;
for( k = 0; k < n/2; ++k )
sf *= 1.0 + fomega * sin((double)(2*k+1)*parg0);
fomega = sin(omega / 2.0);
if( n % 2 ) sf *= fomega + cos(omega / 2.0);
sf = pow( fomega, n ) / sf;
return(sf);
}
/**********************************************************************
sf_bwhp - calculates the scaling factor for a butterworth highpass filter.
The scaling factor is what the c coefficients must be multiplied by so
that the filter response has a maximum value of 1.
*/
static double
sf_bwhp( int n, double fcf )
{
int m, k; // loop variables
double omega; // M_PI * fcf
double fomega; // function of omega
double parg0; // zeroth pole angle
double sf; // scaling factor
omega = M_PI * fcf;
fomega = sin(omega);
parg0 = M_PI / (double)(2*n);
m = n / 2;
sf = 1.0;
for( k = 0; k < n/2; ++k )
sf *= 1.0 + fomega * sin((double)(2*k+1)*parg0);
fomega = cos(omega / 2.0);
if( n % 2 ) sf *= fomega + sin(omega / 2.0);
sf = pow( fomega, n ) / sf;
return(sf);
}
/**********************************************************************
sf_bwbp - calculates the scaling factor for a butterworth bandpass filter.
The scaling factor is what the c coefficients must be multiplied by so
that the filter response has a maximum value of 1.
*/
static double
sf_bwbp( int n, double f1f, double f2f )
{
int k; // loop variables
double ctt; // cotangent of theta
double sfr, sfi; // real and imaginary parts of the scaling factor
double parg; // pole angle
double sparg; // sine of pole angle
double cparg; // cosine of pole angle
double a, b, c; // workspace variables
ctt = 1.0 / tan(M_PI * (f2f - f1f) / 2.0);
sfr = 1.0;
sfi = 0.0;
for( k = 0; k < n; ++k )
{
parg = M_PI * (double)(2*k+1)/(double)(2*n);
sparg = ctt + sin(parg);
cparg = cos(parg);
a = (sfr + sfi)*(sparg - cparg);
b = sfr * sparg;
c = -sfi * cparg;
sfr = b - c;
sfi = a - b - c;
}
return( 1.0 / sfr );
}
/**********************************************************************
sf_bwbs - calculates the scaling factor for a butterworth bandstop filter.
The scaling factor is what the c coefficients must be multiplied by so
that the filter response has a maximum value of 1.
*/
static double
sf_bwbs( int n, double f1f, double f2f )
{
int k; // loop variables
double tt; // tangent of theta
double sfr, sfi; // real and imaginary parts of the scaling factor
double parg; // pole angle
double sparg; // sine of pole angle
double cparg; // cosine of pole angle
double a, b, c; // workspace variables
tt = tan(M_PI * (f2f - f1f) / 2.0);
sfr = 1.0;
sfi = 0.0;
for( k = 0; k < n; ++k )
{
parg = M_PI * (double)(2*k+1)/(double)(2*n);
sparg = tt + sin(parg);
cparg = cos(parg);
a = (sfr + sfi)*(sparg - cparg);
b = sfr * sparg;
c = -sfi * cparg;
sfr = b - c;
sfi = a - b - c;
}
return( 1.0 / sfr );
}

View file

@ -0,0 +1,178 @@
/* libiir/src/libiir/300_common_filters/100_butterworth.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/* Each of the functions below is an impedance-matching layer between the
* Exstrom code (in 000_exstrom_butterworth.c) and libiir. Although there is a
* fair bit of code duplication, there are just enough subtle differences in the
* interface and implementation of the Exstrom code to make abstracting these
* four functions into one more effort than it is worth.
*/
struct iir_coeff_t* iir_butterworth_lowpass(int order,
double gain,
double corner)
{
int i, nc, nd, * ci;
double* d, * c;
struct iir_coeff_t* coeff;
if(order < 1 || corner < 0 || corner > 1) {
errno = EINVAL;
return 0;
}
/* get coefficients from Exstrom code */
d = dcof_bwlp(order, corner);
nd = order + 1;
ci = ccof_bwlp(order);
nc = order + 1;
gain *= sf_bwlp(order, corner);
/* compute scaled c coefficients */
c = malloc(sizeof(double) * nc);
for(i = 0; i < nc; ++i) c[i] = ci[i] * gain;
/* Instantiate filter structure. Note in Exstrom code that d[0] is always
* 1.0 and not used; the Güralp code doesn't represent it, hence the shift
* by 1. */
coeff = iir_coeff_new(nc, c, nd - 1, d + 1);
/* clean up */
free(ci);
free(c);
free(d);
return coeff;
}
struct iir_coeff_t* iir_butterworth_highpass(int order,
double gain,
double corner)
{
int i, nc, nd, * ci;
double* d, * c;
struct iir_coeff_t* coeff;
if(order < 1 || corner < 0 || corner > 1) {
errno = EINVAL;
return 0;
}
/* get coefficients from Exstrom code */
d = dcof_bwhp(order, corner);
nd = order + 1;
ci = ccof_bwhp(order);
nc = order + 1;
gain *= sf_bwhp(order, corner);
/* compute scaled c coefficients */
c = malloc(sizeof(double) * nc);
for(i = 0; i < nc; ++i) c[i] = ci[i] * gain;
/* Instantiate filter structure. Note in Exstrom code that d[0] is always
* 1.0 and not used; the Güralp code doesn't represent it, hence the shift
* by 1. */
coeff = iir_coeff_new(nc, c, nd - 1, d + 1);
/* clean up */
free(ci);
free(c);
free(d);
return coeff;
}
struct iir_coeff_t* iir_butterworth_bandpass(int order,
double gain,
double c1,
double c2)
{
int i, nc, nd, * ci;
double* d, * c;
struct iir_coeff_t* coeff;
if(order < 1 || c1 < 0 || c1 > c2 || c2 > 1) {
errno = EINVAL;
return 0;
}
/* get coefficients from Exstrom code */
d = dcof_bwbp(order, c1, c2);
nd = 2 * order + 1;
ci = ccof_bwbp(order);
nc = 2 * order + 1;
gain *= sf_bwbp(order, c1, c2);
/* compute scaled c coefficients */
c = malloc(sizeof(double) * nc);
for(i = 0; i < nc; ++i) c[i] = ci[i] * gain;
/* Instantiate filter structure. Note in Exstrom code that d[0] is always
* 1.0 and not used; the Güralp code doesn't represent it, hence the shift
* by 1. */
coeff = iir_coeff_new(nc, c, nd - 1, d + 1);
/* clean up */
free(ci);
free(c);
free(d);
return coeff;
}
struct iir_coeff_t* iir_butterworth_bandstop(int order,
double gain,
double c1,
double c2)
{
int i, nc, nd;
double* d, * c;
struct iir_coeff_t* coeff;
if(order < 1 || c1 < 0 || c1 > c2 || c2 > 1) {
errno = EINVAL;
return 0;
}
/* get coefficients from Exstrom code */
d = dcof_bwbs(order, c1, c2);
nd = 2 * order + 1;
c = ccof_bwbs(order, c1, c2);
nc = 2 * order + 1;
gain *= sf_bwbs(order, c1, c2);
/* compute scaled c coefficients */
for(i = 0; i < nc; ++i) c[i] *= gain;
/* Instantiate filter structure. Note in Exstrom code that d[0] is always
* 1.0 and not used; the Güralp code doesn't represent it, hence the shift
* by 1. */
coeff = iir_coeff_new(nc, c, nd - 1, d + 1);
/* clean up */
free(c);
free(d);
return coeff;
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

395
src/libiir/400_parser.c Normal file
View file

@ -0,0 +1,395 @@
/* libiir/src/libiir/400_parser.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/* IIR_PARSER_CHAIN()
* In the parser functions below, we are passed a pointer to the pointer to the
* IIR filter structure. The pointed-to pointer must be set if we are parsing
* the very first set of coefficients, or chained otherwise. (This structure is
* necessary as some of the parser functions result in multiple coefficient
* sets). This macro handles that. _fi is of type struct iir_filter_t**.
*/
#define IIR_PARSER_CHAIN(_fi, _coeff) do { \
if(*_fi) iir_filter_chain(*_fi, _coeff); \
else *_fi = iir_filter_new(_coeff); \
}while(0)
/* iir_parser_raw()
* Parses raw coefficients and adds a filter to the chain fi. desc should
* point at the parameter string; a '/' character splits the c and d
* coefficients, and a ')' character marks the end of the parameters. The aux
* function allocates and parses an array of doubles with each value separated
* by a ',' character and the array terminated by an arbitrary character
* endc.
*/
static double*
iir_parser_raw_aux(int* nout, const char** desc, char endc)
{
int n = 0, sz = 16;
double* c, x;
char* endp;
c = malloc(sizeof(double) * sz);
while(1) {
/* parse a single coefficient, taking care with strtod(3) */
errno = 0;
endp = 0;
x = strtod(*desc, &endp);
if(errno || !endp || endp == *desc) goto fail;
*desc = endp + 1;
/* add to array */
if(n == sz) {
sz <<= 1;
c = realloc(c, sizeof(double) * sz);
}
c[n++] = x;
/* check for ',' or end-of-list char */
if(*endp == ',') continue;
if(*endp != endc) goto fail;
break;
}
/* done */
*nout = n;
return c;
fail:
free(c);
return 0;
}
static int
iir_parser_raw(struct iir_filter_t** fi, const char* desc)
{
int nc, nd;
double* c, * d;
struct iir_coeff_t* coeff;
/* allocate and parse two arrays of double */
c = iir_parser_raw_aux(&nc, &desc, '/');
if(!c) return -1;
d = iir_parser_raw_aux(&nd, &desc, ')');
if(!d) {
free(c);
return -1;
}
/* HACK: rather than calling iir_coeff_new(), save time by not creating a
* redundant copy and just fill the structure directly */
coeff = malloc(sizeof(struct iir_coeff_t));
coeff->nc = nc;
coeff->nd = nd;
coeff->c = c;
coeff->d = d;
IIR_PARSER_CHAIN(fi, coeff);
iir_coeff_free(coeff);
return 0;
}
/* IIR_PARSER_BW_MAX_ORDER
* The maximum order in any single Butterworth-type filter. If the given order
* exceeds this, we split the resulting filter up into multiple sets of
* coefficients of this order or less.
*/
#define IIR_PARSER_BW_MAX_ORDER (4)
/* iir_parser_bw_aux()
* Parses the Butterworth-type parameter string. c2 may be passed as null if
* the filter only has one corner frequency (low or high pass).
*/
static int
iir_parser_bw_aux(const char* desc,
int* order,
double* gain,
double* c1,
double* c2)
{
char* endp;
/* parse order,gain,c1 */
errno = 0;
endp = 0;
*order = strtol(desc, &endp, 0);
if(errno || !endp || endp == desc || *endp != ',') return -1;
desc = endp + 1;
endp = 0;
*gain = strtod(desc, &endp);
if(errno || !endp || endp == desc || *endp != ',') return -1;
desc = endp + 1;
endp = 0;
*c1 = strtod(desc, &endp);
if(errno || !endp || endp == desc) return -1;
desc = endp + 1;
/* if c2 is requested, parse that as well */
if(c2) {
if(*endp != ',') return -1;
endp = 0;
*c2 = strtod(desc, &endp);
if(errno || !endp || endp == desc) return -1;
}
/* check we used the entire parameter string */
return *endp != ')';
}
/* iir_parser_bw_aux2()
* Instantiates a low/high pass type filter with a single corner frequency
* using the function bw. Splits into multiple filters if the order exceeds
* the threshold in IIR_PARSER_BW_MAX_ORDER.
*/
static int
iir_parser_bw_aux2(struct iir_filter_t** fi,
struct iir_coeff_t* (*bw)(int, double, double),
int order,
double gain,
double corner)
{
struct iir_coeff_t* coeff;
/* split into segments of 4th order or less */
if(order >= IIR_PARSER_BW_MAX_ORDER) {
coeff = bw(IIR_PARSER_BW_MAX_ORDER, gain, corner);
if(!coeff) return -1;
while(order >= IIR_PARSER_BW_MAX_ORDER) {
IIR_PARSER_CHAIN(fi, coeff);
order -= IIR_PARSER_BW_MAX_ORDER;
}
iir_coeff_free(coeff);
if(!order) return 0;
/* add a <4th order segment */
}
coeff = bw(order, gain, corner);
if(!coeff) return -1;
IIR_PARSER_CHAIN(fi, coeff);
iir_coeff_free(coeff);
return 0;
}
/* iir_parser_bw_aux3()
* Instantiates a band pass/stop type filter with two corner frequencies
* using the function bw. Splits into multiple filters if the order exceeds
* the threshold in IIR_PARSER_BW_MAX_ORDER.
*/
static int
iir_parser_bw_aux3(struct iir_filter_t** fi,
struct iir_coeff_t* (*bw)(int, double, double, double),
int order,
double gain,
double c1,
double c2)
{
struct iir_coeff_t* coeff;
/* split into segments of 4th order or less */
if(order >= IIR_PARSER_BW_MAX_ORDER) {
coeff = bw(IIR_PARSER_BW_MAX_ORDER, gain, c1, c2);
if(!coeff) return -1;
while(order >= IIR_PARSER_BW_MAX_ORDER) {
IIR_PARSER_CHAIN(fi, coeff);
order -= IIR_PARSER_BW_MAX_ORDER;
}
iir_coeff_free(coeff);
if(!order) return 0;
/* add a <4th order segment */
}
coeff = bw(order, gain, c1, c2);
if(!coeff) return -1;
IIR_PARSER_CHAIN(fi, coeff);
iir_coeff_free(coeff);
return 0;
}
/* iir_parser_bw*()
* Various Butterworth-type parsers, built out of the aux blocks above.
*/
static int
iir_parser_bwlp(struct iir_filter_t** fi, const char* desc)
{
int order;
double gain, corner;
/* parse order,gain,corner */
if(iir_parser_bw_aux(desc, &order, &gain, &corner, 0)) return -1;
/* instantiate and associate coefficients */
return iir_parser_bw_aux2(fi, iir_butterworth_lowpass, order, gain, corner);
}
static int
iir_parser_bwhp(struct iir_filter_t** fi, const char* desc)
{
int order;
double gain, corner;
/* parse order,gain,corner */
if(iir_parser_bw_aux(desc, &order, &gain, &corner, 0)) return -1;
/* instantiate and associate coefficients */
return iir_parser_bw_aux2(fi, iir_butterworth_highpass, order, gain, corner);
}
static int
iir_parser_bwbp(struct iir_filter_t** fi, const char* desc)
{
int order;
double gain, c1, c2;
/* parse order,gain,corner */
if(iir_parser_bw_aux(desc, &order, &gain, &c1, &c2)) return -1;
/* instantiate and associate coefficients */
return iir_parser_bw_aux3(fi, iir_butterworth_bandpass, order, gain, c1, c2);
}
static int
iir_parser_bwbs(struct iir_filter_t** fi, const char* desc)
{
int order;
double gain, c1, c2;
/* parse order,gain,corner */
if(iir_parser_bw_aux(desc, &order, &gain, &c1, &c2)) return -1;
/* instantiate and associate coefficients */
return iir_parser_bw_aux3(fi, iir_butterworth_bandstop, order, gain, c1, c2);
}
struct iir_parser_t {
const char* type;
int (*parse)(struct iir_filter_t** fi, const char* desc);
};
static struct iir_parser_t iir_parsers[] = {
{
.type = "raw",
.parse = iir_parser_raw,
},
{
.type = "butterworth_lowpass",
.parse = iir_parser_bwlp,
},
{
.type = "butterworth_highpass",
.parse = iir_parser_bwhp,
},
{
.type = "butterworth_bandpass",
.parse = iir_parser_bwbp,
},
{
.type = "butterworth_bandstop",
.parse = iir_parser_bwbs,
},
{
.type = 0,
}
};
struct iir_filter_t*
iir_parse(const char* desc)
{
const char* p, * q;
int i;
struct iir_filter_t* fi = 0;
while(*desc) {
/* move to next filter in chain */
if(isspace(*desc)) {
++desc;
continue;
}
/* verify type(args) layout */
p = strchr(desc, '(');
q = strchr(desc, ')');
if(!p || !q || q < p) goto fail;
/* find matching parser function */
for(i = 0; iir_parsers[i].type; ++i) {
if((long)strlen(iir_parsers[i].type) != p - desc) continue;
if(memcmp(iir_parsers[i].type, desc, p - desc)) continue;
break;
}
if(!iir_parsers[i].type) goto fail;
/* parse description, add to chain */
if(iir_parsers[i].parse(&fi, p + 1)) goto fail;
/* consume this filter description from string */
desc = q + 1;
}
/* finish up */
if(!fi) {
errno = EINVAL;
return 0;
}
return fi;
fail:
iir_filter_free(fi);
errno = EINVAL;
return 0;
}
struct iir_filter_t**
iir_parse_n(const char* desc, int n)
{
struct iir_filter_t* fi, ** a;
int i;
if(n < 1) {
errno = EINVAL;
return 0;
}
fi = iir_parse(desc);
if(!fi) return 0;
a = malloc(sizeof(struct iir_filter_t*) * n);
a[0] = fi;
for(i = 1; i < n; ++i) a[n] = iir_filter_copy(fi, 0);
return a;
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

71
src/libiir/400_parser.h Normal file
View file

@ -0,0 +1,71 @@
/* libiir/src/libiir/400_parser.h
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
/*! \defgroup parser Parser for user-specified IIR filters
This is a high-level interface that can instantiate a set of IIR filters based
on a user-specified, human-readable string. The intention of this interface is
to allow IIR filters to be specified in configuration files so that they can be
easily modified by the user and easily understood/parsed by the system.
See \ref string_desc for details on the string description format.
*/
/*!@{*/
/*! \brief Instantiate an IIR filter based on a string description
\param desc IIR filter description.
\returns Pointer to newly-allocated IIR filter instance.
\retval 0 on error.
Parses the human-readable description of an IIR filter chain in \a desc,
instantiating an IIR filter object to match. Returns the new filter. If \a desc
cannot be parsed correctly, returns 0 and sets \a errno to \c EINVAL.
*/
struct iir_filter_t* iir_parse(const char* desc)
#ifndef DOXYGEN
__attribute__((malloc,nonnull))
#endif
;
/*! \brief Instantiate a set of IIR filters based on a string description
\param desc IIR filter description.
\param n Number of instances to allocate.
\returns Pointer to array of \a n newly-allocated IIR filter instances.
\retval 0 on error.
Parses the human-readable description of an IIR filter chain in \a desc,
instantiating a set of \a n identical IIR filter objects to match. Returns a
pointer to an array of new filters. If \a desc cannot be parsed correctly,
returns 0 and sets \a errno to \c EINVAL.
The user is responsible for freeing both the array elements (with
\ref iir_filter_free()) and the array itself (with \c free(3)).
*/
struct iir_filter_t** iir_parse_n(const char* desc, int n)
#ifndef DOXYGEN
__attribute__((malloc,nonnull))
#endif
;
/*!@}*/
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

View file

@ -0,0 +1,13 @@
/* libiir/src/libiir/999_BottomHeader.h
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#endif
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4:syntax=c.doxygen
*/

1
src/libiir/build.default Normal file
View file

@ -0,0 +1 @@
source src/libiir/build.lib

1
src/libiir/build.install Normal file
View file

@ -0,0 +1 @@
source src/libiir/build.install-lib

View file

@ -0,0 +1,38 @@
build_target libiir
# make paths (this is for Gentoo in particular)
build_dir_tree "${LIBDIR}" || return 1
build_dir_tree "${BINDIR}" || return 1
build_dir_tree "${INCLUDEDIR}" || return 1
# install library
echo "Installing libraries into '${LIBDIR}'"
source src/libiir/soversion
install_file ${libiir} ${LIBDIR} 0755 || return 1
BASE="${libiir_BASE}.so"
MAJOR="${BASE}.${SOMAJOR}"
MICRO="${MAJOR}.${SOMICRO}"
install_symlink "${BASE}" "${MICRO}" "${LIBDIR}"
# install header
echo "Installing header file '${libiir_HEADER}' into ${INCLUDEDIR}"
install_header ${libiir_HEADER} ${INCLUDEDIR} 0644 || return 1
# install config script
echo "Installing config script into ${BINDIR}"
CONFFILE="${INSTALL_PREFIX}${BINDIR}/libiir-config"
do_cmd rm -f "${CONFFILE}"
do_cmd_redir "${CONFFILE}" sed \
-e "s,@VERSION@,${VERSION}," \
-e "s,@DEP_CFLAGS@,${libiir_DEP_CFLAGS}," \
-e "s,@DEP_LIBS@,${libiir_DEP_LIBS}," \
-e "s,@LIB_DIR@,${LIBDIR}," \
-e "s,@INCLUDE_DIR@,${INCLUDEDIR}," \
src/libiir/config-script
do_cmd chmod 0755 "${CONFFILE}"
print_success "Done"
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

57
src/libiir/build.lib Normal file
View file

@ -0,0 +1,57 @@
# These are external variables, and shouldn't clash with anything else
# libiir
# libiir_BUILT
# libiir_HEADER
# libiir_BASE
# libiir_DEP_CFLAGS
# libiir_DEP_LIBS
if [ -z ${libiir_BUILT} ]
then
libiir_BASE=libiir
source src/libiir/soversion
libiir="obj/${libiir_BASE}.so.${SOMAJOR}.${SOMICRO}"
libiir_DEP_CFLAGS=""
libiir_DEP_LIBS="-lm"
SO_EXTRA="-std=gnu99 -D_GNU_SOURCE \
${libiir_DEP_CFLAGS} ${libiir_DEP_LIBS} -lc"
echo "Building library ${libiir}..."
do_cmd source src/libiir/build.monolithic || return 1
MODIFIED=0
for test in ${MONOLITHIC_TESTS} ${HDR} ${SRC}
do
if [ ${test} -nt ${libiir} ]
then
MODIFIED=1
break
fi
done
if [ ${MODIFIED} -ne 0 ]
then
echo " Compiling"
SONAME="${libiir_BASE}.so.${SOMAJOR}"
do_cmd ${CC} ${CFLAGS} -Iobj -shared -fpic -o "${libiir}" \
-Wl,-soname,${SONAME} \
${SRC} ${SO_EXTRA} || return 1
# make tests and linking work
do_cmd ln -sf "$(basename "${libiir}")" "obj/${SONAME}" || return 1
do_cmd ln -sf "$(basename "${libiir}")" "obj/${libiir_BASE}.so" || return 1
print_success "Library built"
else
print_success "Library up to date"
fi
libiir_BUILT=1
libiir_HEADER=${HDR}
fi
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

View file

@ -0,0 +1,21 @@
# These are external variables, and shouldn't clash with anything else
# libiir_MONOLITHIC
SRC="obj/libiir.c"
HDR="obj/iir.h"
MONOLITHIC_TESTS="src/libiir/build.lib src/libiir/build.monolithic"
if [ -z "${libiir_MONOLITHIC}" ]
then
MONOLITHIC_SOURCE="$(find src/libiir/ -name '*.h' | sort)"
make_monolithic ${HDR} Ch || return 1
MONOLITHIC_SOURCE="$(find src/libiir/ -name '*.c' | sort)"
make_monolithic ${SRC} C || return 1
libiir_MONOLITHIC=1
MONOLITHIC_DOC="${MONOLITHIC_DOC} ${HDR}"
fi
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

98
src/libiir/config-script Normal file
View file

@ -0,0 +1,98 @@
#!/bin/bash
# libiir/src/libiir/config-script
#
# libiir-config template. Variables are finalised at install time.
#
dep_cflags="@DEP_CFLAGS@"
dep_libs="@DEP_LIBS@"
include_dir="@INCLUDE_DIR@"
include_dir_set="no"
lib_dir="@LIB_DIR@"
lib_dir_set="no"
usage() {
cat <<EOF
Usage: libiir-config [options]
Options:
[--version]
[--libs]
[--libdir[=DIR]]
[--cflags]
[--includedir[=DIR]]
EOF
exit $1
}
[ $# -eq 0 ] && usage 1 1>&2
while [ $# -gt 0 ]
do
case "$1" in
-*=*)
optarg="$(echo "$1" | sed 's/[-_a-zA-Z0-9]*=//')"
;;
*)
optarg=""
;;
esac
case "$1" in
--libdir=*)
lib_dir="${optarg}"
lib_dir_set="yes"
;;
--libdir)
echo_lib_dir="yes"
;;
--includedir=*)
include_dir="${optarg}"
include_dir_set="yes"
;;
--includedir)
echo_include_dir="yes"
;;
--version)
echo "@VERSION@"
exit 0
;;
--cflags)
[ "${include_dir}" != "/usr/include" ] && includes="-I${include_dir}"
echo_cflags="yes"
;;
--libs)
echo_libs="yes"
;;
*)
usage 1 1>&2
;;
esac
shift
done
[ "${echo_prefix}" == "yes" ] && echo "${prefix}"
[ "${echo_exec_prefix}" == "yes" ] && echo "${exec_prefix}"
[ "${echo_cflags}" == "yes" ] && echo "${dep_cflags} ${includes}"
[ "${echo_libs}" == "yes" ] && echo "${dep_libs} -L${lib_dir} -liir"
true
# vim: syntax=sh:expandtab:ts=4:sw=4
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;

15
src/libiir/soversion Normal file
View file

@ -0,0 +1,15 @@
# libiir/src/libiir/soversion
#
# (c)2010, Laurence Withers, <l@lwithers.me.uk>.
# Released under the GNU GPLv3. See file COPYING or
# http://www.gnu.org/copyleft/gpl.html for details.
#
# SOMAJOR is included in the library's soname, and needs to be bumped
# after a binary-incompatible release. It is a single integer.
SOMAJOR=0
# SOMICRO is bumped every time there is a binary-compatible release.
SOMICRO=0

1
src/tests/.params Normal file
View file

@ -0,0 +1 @@
tests c tests libiir

3
src/tests/build.default Normal file
View file

@ -0,0 +1,3 @@
source src/tests/build.tests
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

43
src/tests/build.tests Normal file
View file

@ -0,0 +1,43 @@
# These are external variables, and shouldn't clash with anything else
# tests_BUILT
#
build_target libiir || return 1
if [ -z ${tests_BUILT} ]
then
LIBS="${libiir} ${libiir_DEP_CFLAGS} ${libiir_DEP_LIBS} "
EXTRAS="-D_GNU_SOURCE -std=gnu99"
echo "Building test programs..."
do_cmd mkdir -p obj/tests || return 1
for SRC in src/tests/*.c
do
TEST="obj/tests/$(basename "${SRC}" ".c")"
MODIFIED=0
for file in ${LIBS} ${SRC} src/tests/build.tests
do
if [ ${file} -nt ${TEST} ]
then
MODIFIED=1
break
fi
done
if [ ${MODIFIED} -ne 0 ]
then
do_cmd ${CC} -Iobj ${CFLAGS} -o ${TEST} ${SRC} ${LIBS} ${EXTRAS} || return 1
print_success "Built ${TEST}"
else
print_success "${TEST} is up to date"
fi
done
print_success "All tests built"
tests_BUILT=1
fi
# kate: replace-trailing-space-save true; space-indent true; tab-width 4;
# vim: syntax=sh:expandtab:ts=4:sw=4

271
src/tests/plot_filter.c Normal file
View file

@ -0,0 +1,271 @@
/* libiir/src/tests/plot_filter.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#include "iir.h"
#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define NPOINTS (1000)
#define STEADY_STATE_CYCLES (20)
char* tmp_fname;
void
unlink_tmpfile(void)
{
unlink(tmp_fname);
}
int
do_plot(const char* filter_desc, double samp_rat, const char* png_filename)
{
int fd, ret;
FILE* fp;
char cmd_file[] = "/tmp/libiir-plot_filter.cmd.XXXXXX",
cmd[200];
fd = mkstemp(cmd_file);
if(fd == -1) {
perror("mkstemp");
return -1;
}
fp = fdopen(fd, "w");
fprintf(fp, "set terminal png size 1000,1000\n"
"set output '%s'\n"
"set multiplot layout 2,1 title \"Bode plot for filter '",
png_filename);
ret = 0;
while(*filter_desc) {
if(isspace(*filter_desc)) {
if(!ret) {
ret = 1;
putc('\n', fp);
}
} else {
putc(*filter_desc, fp);
}
++filter_desc;
}
fprintf(fp, "' at %fHz\"\n"
"set grid\n"
"set logscale\n"
"set ytics add ('-3dB' %f)\n"
"set xlabel 'Frequency (Hz)'\n"
"set ylabel 'Gain'\n"
"plot '%s' using 1:2 notitle\n"
"unset logscale y\n"
"set yrange [-180:180]\n"
"set ytics -180,45,180\n"
"set ylabel 'Phase (degrees)'\n"
"plot '%s' using 1:3 notitle\n",
samp_rat,
pow(10, -3.0/20),
tmp_fname,
tmp_fname);
fclose(fp);
snprintf(cmd, sizeof(cmd), "gnuplot %s", cmd_file);
ret = system(cmd);
unlink(cmd_file);
return ret;
}
double
compute_magnitude(double* y, int nsamp)
{
int samp;
double max, min;
max = min = y[0];
for(samp = 1; samp < nsamp; ++samp) {
if(y[samp] > max) max = y[samp];
if(y[samp] < min) min = y[samp];
}
return (max - min) / 2;
}
double
compute_phase_deg(double* x, double* y, int nsamp)
{
int samp, xphase = 0, yphase = 0;
double xmax, ymax, phase;
xmax = x[0];
ymax = y[0];
for(samp = 1; samp < nsamp; ++samp) {
if(x[samp] > xmax) {
xmax = x[samp];
xphase = samp;
}
if(y[samp] > ymax) {
ymax = y[samp];
yphase = samp;
}
}
phase = (xphase - yphase) * 360.0 / nsamp;
if(phase > 180) phase -= 360;
if(phase <= -180) phase += 360;
return phase;
}
double
interp(int step, int max, double start, double end)
{
return start + step * ((end - start) / (max - 1));
}
void
calc_response(FILE* fp,
struct iir_filter_t* orig_fi,
double samp_rat,
double start_freq,
double end_freq)
{
int step, samp, cycle_len;
double freq;
struct iir_filter_t* fi;
static double* x = 0, * y = 0;
for(step = 0; step < NPOINTS; ++step) {
freq = exp(interp(step, NPOINTS, log(start_freq), log(end_freq)));
cycle_len = samp_rat / freq + 1;
/* HACK: allocate persistent buffer; first call must have lowest freq */
if(!x) {
x = malloc(sizeof(double) * cycle_len);
y = malloc(sizeof(double) * cycle_len);
}
/* HACK: build steady-state filter response */
fi = iir_filter_copy(orig_fi, 0);
for(samp = 0; samp < cycle_len * STEADY_STATE_CYCLES; ++samp) {
iir_filter(fi, sin(2 * M_PI * freq / samp_rat * samp));
}
/* run and record one complete cycle */
for(samp = 0; samp < cycle_len; ++samp) {
x[samp] = sin(2 * M_PI * freq / samp_rat *
(samp + cycle_len * STEADY_STATE_CYCLES));
y[samp] = iir_filter(fi, x[samp]);
}
iir_filter_free(fi);
fprintf(fp, "%e\t%e\t% 6.2f\n",
freq,
compute_magnitude(y, cycle_len),
compute_phase_deg(x, y, cycle_len));
}
}
int
safe_strtod(const char* str, double* d)
{
char* endp = 0;
errno = 0;
*d = strtod(str, &endp);
if(errno || !endp || *endp) return -1;
return 0;
}
int
main(int argc, char* argv[])
{
int fd;
double samp_rat, start_freq, end_freq;
FILE* fp;
struct iir_filter_t* fi;
/* process commandline arguments */
if(argc == 2 && !strcmp(argv[1], "--print-summary")) {
fputs("Generates Bode plot for a filter.\n", stdout);
return 0;
}
if(argc != 6) {
fputs("Usage: plot_filter 'filter_desc' samp_rat start_freq end_freq out.png\n",
stderr);
return 1;
}
fi = iir_parse(argv[1]);
if(!fi) {
fputs("Invalid filter description string.\n", stderr);
return 1;
}
if(safe_strtod(argv[2], &samp_rat) || samp_rat < 1e-6) {
fputs("Invalid sample rate. Positive float in Hz.\n", stderr);
return 1;
}
if(safe_strtod(argv[3], &start_freq) || start_freq < 1e-6) {
fputs("Invalid start frequency. Positive float in Hz.\n", stderr);
return 1;
}
if(safe_strtod(argv[4], &end_freq) || end_freq < 1e-6
|| end_freq > samp_rat || end_freq < start_freq)
{
fputs("Invalid end frequency. Positive float in Hz, less than sample\n"
"rate, but greater than start frequency.\n", stderr);
return 1;
}
/* create temporary file for results; gnuplot will use this */
tmp_fname = strdup("/tmp/libiir-plot_filter.data.XXXXXX");
fd = mkstemp(tmp_fname);
if(fd == -1) {
perror("mkstemp");
return 1;
}
atexit(unlink_tmpfile);
fp = fdopen(fd, "w");
calc_response(fp, fi, samp_rat, start_freq, end_freq);
fclose(fp);
/* clean up (for valgrind) */
iir_filter_free(fi);
/* draw the plot */
return do_plot(argv[1], samp_rat, argv[5]);
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

77
src/tests/run_filter.c Normal file
View file

@ -0,0 +1,77 @@
/* libiir/src/tests/run_filter.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#include "iir.h"
#include <stdio.h>
#include <string.h>
int
main(int argc, char* argv[])
{
FILE* outf, * inf;
struct iir_filter_t* iir;
double samp;
outf = stdout;
inf = stdin;
/* process commandline arguments */
if(argc == 2 && !strcmp(argv[1], "--print-summary")) {
fputs("Runs an IIR filter on an input stream.\n", stdout);
return 0;
}
switch(argc) {
case 4:
outf = fopen(argv[3], "w");
if(!outf) {
perror(argv[3]);
return 1;
}
/* fall through */
case 3:
inf = fopen(argv[2], "r");
if(!inf) {
perror(argv[2]);
return 1;
}
/* fall through */
case 2:
iir = iir_parse(argv[1]);
if(!iir) {
fputs("Invalid filter description string.\n", stderr);
return 1;
}
break;
default:
fputs("Usage: run_filter 'filter desc' [infile [outfile]]\n", stdout);
return 1;
}
/* run filter on our input */
while(fscanf(inf, " %lf", &samp) == 1) {
fprintf(outf, "%f\n", iir_filter(iir, samp));
}
/* clean up (for valgrind) */
if(outf != stdout) fclose(outf);
if(inf != stdin) fclose(inf);
iir_filter_free(iir);
return 0;
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

39
src/tests/template Normal file
View file

@ -0,0 +1,39 @@
/* libiir/src/tests/???.c
*
* Copyright: ©2010, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
#include "iir.h"
#include <stdio.h>
#include <string.h>
int
main(int argc, char* argv[])
{
int ret = 0;
if(argc == 2 && !strcmp(argv[1], "--print-summary")) {
fputs("One line summary.\n", stdout);
return 0;
}
if(argc == 1) {
/* empty argument list */
}
/* TODO */
return ret;
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/