summaryrefslogtreecommitdiff
path: root/libs/audiographer
diff options
context:
space:
mode:
authorSakari Bergen <sakari.bergen@beatwaves.net>2009-12-27 14:46:23 +0000
committerSakari Bergen <sakari.bergen@beatwaves.net>2009-12-27 14:46:23 +0000
commitdde0848a984e06cbc1d4117d9cffa75c191f3b39 (patch)
tree11f3a5fe94ac792e753297e16e4e80dd7e296aea /libs/audiographer
parent35c72a53b4c6bbc61b4b86db9de629e18362b48d (diff)
Re-integrate export-optimization branch.
Export now happens directly to file (unless normalizing is required), and can be easily optimized even further. The Session process connection is still broken during export (as it was before this commit also). git-svn-id: svn://localhost/ardour2/branches/3.0@6401 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/audiographer')
-rw-r--r--libs/audiographer/COPYING340
-rw-r--r--libs/audiographer/audiographer/chunker.h54
-rw-r--r--libs/audiographer/audiographer/deinterleaver-inl.h78
-rw-r--r--libs/audiographer/audiographer/deinterleaver.h46
-rw-r--r--libs/audiographer/audiographer/exception.h52
-rw-r--r--libs/audiographer/audiographer/identity_vertex.h21
-rw-r--r--libs/audiographer/audiographer/interleaver-inl.h92
-rw-r--r--libs/audiographer/audiographer/interleaver.h71
-rw-r--r--libs/audiographer/audiographer/listed_source.h50
-rw-r--r--libs/audiographer/audiographer/normalizer.h82
-rw-r--r--libs/audiographer/audiographer/peak_reader.h38
-rw-r--r--libs/audiographer/audiographer/process_context.h154
-rw-r--r--libs/audiographer/audiographer/routines.h53
-rw-r--r--libs/audiographer/audiographer/sample_format_converter.h67
-rw-r--r--libs/audiographer/audiographer/silence_trimmer.h191
-rw-r--r--libs/audiographer/audiographer/sink.h42
-rw-r--r--libs/audiographer/audiographer/sndfile_base.h30
-rw-r--r--libs/audiographer/audiographer/sndfile_reader.h40
-rw-r--r--libs/audiographer/audiographer/sndfile_writer.h29
-rw-r--r--libs/audiographer/audiographer/source.h28
-rw-r--r--libs/audiographer/audiographer/sr_converter.h51
-rw-r--r--libs/audiographer/audiographer/threader.h120
-rw-r--r--libs/audiographer/audiographer/tmp_file.h25
-rw-r--r--libs/audiographer/audiographer/types.h32
-rw-r--r--libs/audiographer/audiographer/utils.h59
-rw-r--r--libs/audiographer/autowaf.py374
-rw-r--r--libs/audiographer/src/gdither/gdither.cc474
-rw-r--r--libs/audiographer/src/gdither/gdither.h92
-rw-r--r--libs/audiographer/src/gdither/gdither_types.h48
-rw-r--r--libs/audiographer/src/gdither/gdither_types_internal.h74
-rw-r--r--libs/audiographer/src/gdither/noise.h38
-rw-r--r--libs/audiographer/src/routines.cc7
-rw-r--r--libs/audiographer/src/sample_format_converter.cc190
-rw-r--r--libs/audiographer/src/sndfile_base.cc56
-rw-r--r--libs/audiographer/src/sndfile_reader.cc67
-rw-r--r--libs/audiographer/src/sndfile_writer.cc73
-rw-r--r--libs/audiographer/src/sr_converter.cc218
-rw-r--r--libs/audiographer/src/utils.cc14
-rw-r--r--libs/audiographer/tests/chunker_test.cc112
-rw-r--r--libs/audiographer/tests/deinterleaver_test.cc133
-rw-r--r--libs/audiographer/tests/identity_vertex_test.cc99
-rw-r--r--libs/audiographer/tests/interleaver_deinterleaver_test.cc120
-rw-r--r--libs/audiographer/tests/interleaver_test.cc132
-rw-r--r--libs/audiographer/tests/normalizer_test.cc60
-rw-r--r--libs/audiographer/tests/peak_reader_test.cc53
-rw-r--r--libs/audiographer/tests/sample_format_converter_test.cc209
-rw-r--r--libs/audiographer/tests/silence_trimmer_test.cc196
-rw-r--r--libs/audiographer/tests/sndfile_writer_test.cc42
-rw-r--r--libs/audiographer/tests/sr_converter_test.cc101
-rw-r--r--libs/audiographer/tests/test_runner.cc14
-rw-r--r--libs/audiographer/tests/threader_test.cc155
-rw-r--r--libs/audiographer/tests/utils.h119
-rw-r--r--libs/audiographer/wscript118
53 files changed, 5233 insertions, 0 deletions
diff --git a/libs/audiographer/COPYING b/libs/audiographer/COPYING
new file mode 100644
index 0000000000..d60c31a97a
--- /dev/null
+++ b/libs/audiographer/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/libs/audiographer/audiographer/chunker.h b/libs/audiographer/audiographer/chunker.h
new file mode 100644
index 0000000000..afce921cc2
--- /dev/null
+++ b/libs/audiographer/audiographer/chunker.h
@@ -0,0 +1,54 @@
+#ifndef AUDIOGRAPHER_CHUNKER_H
+#define AUDIOGRAPHER_CHUNKER_H
+
+#include "listed_source.h"
+#include "sink.h"
+#include <cstring>
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class Chunker : public ListedSource<T>, public Sink<T>
+{
+ public:
+ Chunker (nframes_t chunk_size)
+ : chunk_size (chunk_size)
+ , position (0)
+ {
+ buffer = new T[chunk_size];
+ }
+
+ ~Chunker()
+ {
+ delete [] buffer;
+ }
+
+ void process (ProcessContext<T> const & context)
+ {
+ if (position + context.frames() < chunk_size) {
+ memcpy (&buffer[position], (float const *)context.data(), context.frames() * sizeof(T));
+ position += context.frames();
+ } else {
+ nframes_t const frames_to_copy = chunk_size - position;
+ memcpy (&buffer[position], context.data(), frames_to_copy * sizeof(T));
+ ProcessContext<T> c_out (context, buffer, chunk_size);
+ ListedSource<T>::output (c_out);
+
+ memcpy (buffer, &context.data()[frames_to_copy], (context.frames() - frames_to_copy) * sizeof(T));
+ position = context.frames() - frames_to_copy;
+ }
+ }
+ using Sink<T>::process;
+
+ private:
+ nframes_t chunk_size;
+ nframes_t position;
+ T * buffer;
+
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_CHUNKER_H
+
diff --git a/libs/audiographer/audiographer/deinterleaver-inl.h b/libs/audiographer/audiographer/deinterleaver-inl.h
new file mode 100644
index 0000000000..f93fdc53a4
--- /dev/null
+++ b/libs/audiographer/audiographer/deinterleaver-inl.h
@@ -0,0 +1,78 @@
+template<typename T>
+DeInterleaver<T>::DeInterleaver()
+ : channels (0)
+ , max_frames (0)
+ , buffer (0)
+ {}
+
+template<typename T>
+void
+DeInterleaver<T>::init (unsigned int num_channels, nframes_t max_frames_per_channel)
+{
+ reset();
+ channels = num_channels;
+ max_frames = max_frames_per_channel;
+ buffer = new T[max_frames];
+
+ for (unsigned int i = 0; i < channels; ++i) {
+ outputs.push_back (OutputPtr (new IdentityVertex<T>));
+ }
+}
+
+template<typename T>
+typename DeInterleaver<T>::SourcePtr
+DeInterleaver<T>::output (unsigned int channel)
+{
+ if (channel >= channels) {
+ throw Exception (*this, "channel out of range");
+ }
+
+ return boost::static_pointer_cast<Source<T> > (outputs[channel]);
+}
+
+template<typename T>
+void
+DeInterleaver<T>::process (ProcessContext<T> const & c)
+{
+ nframes_t frames = c.frames();
+ T const * data = c.data();
+
+ if (frames == 0) { return; }
+
+ nframes_t const frames_per_channel = frames / channels;
+
+ if (c.channels() != channels) {
+ throw Exception (*this, "wrong amount of channels given to process()");
+ }
+
+ if (frames % channels != 0) {
+ throw Exception (*this, "wrong amount of frames given to process()");
+ }
+
+ if (frames_per_channel > max_frames) {
+ throw Exception (*this, "too many frames given to process()");
+ }
+
+ unsigned int channel = 0;
+ for (typename std::vector<OutputPtr>::iterator it = outputs.begin(); it != outputs.end(); ++it, ++channel) {
+ if (!*it) { continue; }
+
+ for (unsigned int i = 0; i < frames_per_channel; ++i) {
+ buffer[i] = data[channel + (channels * i)];
+ }
+
+ ProcessContext<T> c_out (c, buffer, frames_per_channel, 1);
+ (*it)->process (c_out);
+ }
+}
+
+template<typename T>
+void
+DeInterleaver<T>::reset ()
+{
+ outputs.clear();
+ delete [] buffer;
+ buffer = 0;
+ channels = 0;
+ max_frames = 0;
+}
diff --git a/libs/audiographer/audiographer/deinterleaver.h b/libs/audiographer/audiographer/deinterleaver.h
new file mode 100644
index 0000000000..3a4aa53c43
--- /dev/null
+++ b/libs/audiographer/audiographer/deinterleaver.h
@@ -0,0 +1,46 @@
+#ifndef AUDIOGRAPHER_DEINTERLEAVER_H
+#define AUDIOGRAPHER_DEINTERLEAVER_H
+
+#include "types.h"
+#include "source.h"
+#include "sink.h"
+#include "identity_vertex.h"
+#include "exception.h"
+
+#include <vector>
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class DeInterleaver : public Sink<T>
+{
+ private:
+ typedef boost::shared_ptr<IdentityVertex<T> > OutputPtr;
+
+ public:
+ DeInterleaver();
+ ~DeInterleaver() { reset(); }
+
+ typedef boost::shared_ptr<Source<T> > SourcePtr;
+
+ void init (unsigned int num_channels, nframes_t max_frames_per_channel);
+ SourcePtr output (unsigned int channel);
+ void process (ProcessContext<T> const & c);
+ using Sink<T>::process;
+
+ private:
+
+ void reset ();
+
+ std::vector<OutputPtr> outputs;
+ unsigned int channels;
+ nframes_t max_frames;
+ T * buffer;
+};
+
+#include "deinterleaver-inl.h"
+
+} // namespace
+
+#endif // AUDIOGRAPHER_DEINTERLEAVER_H
diff --git a/libs/audiographer/audiographer/exception.h b/libs/audiographer/audiographer/exception.h
new file mode 100644
index 0000000000..a179b30f91
--- /dev/null
+++ b/libs/audiographer/audiographer/exception.h
@@ -0,0 +1,52 @@
+#ifndef AUDIOGRAPHER_EXCEPTION_H
+#define AUDIOGRAPHER_EXCEPTION_H
+
+#include <exception>
+#include <string>
+#include <cxxabi.h>
+
+#include <boost/format.hpp>
+
+namespace AudioGrapher
+{
+
+class Exception : public std::exception
+{
+ public:
+ template<typename T>
+ Exception (T const & thrower, std::string const & reason)
+ : reason (boost::str (boost::format (
+ "Exception thrown by %1%: %2%") % name (thrower) % reason))
+ {}
+
+ virtual ~Exception () throw() { }
+
+ const char* what() const throw()
+ {
+ return reason.c_str();
+ }
+
+ protected:
+ template<typename T>
+ std::string name (T const & obj)
+ {
+#ifdef __GNUC__
+ int status;
+ char * res = abi::__cxa_demangle (typeid(obj).name(), 0, 0, &status);
+ if (status == 0) {
+ std::string s(res);
+ free (res);
+ return s;
+ }
+#endif
+ return typeid(obj).name();
+ }
+
+ private:
+ std::string const reason;
+
+};
+
+} // namespace AudioGrapher
+
+#endif // AUDIOGRAPHER_EXCEPTION_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/identity_vertex.h b/libs/audiographer/audiographer/identity_vertex.h
new file mode 100644
index 0000000000..b53bd96851
--- /dev/null
+++ b/libs/audiographer/audiographer/identity_vertex.h
@@ -0,0 +1,21 @@
+#ifndef AUDIOGRAPHER_IDENTITY_VERTEX_H
+#define AUDIOGRAPHER_IDENTITY_VERTEX_H
+
+#include "listed_source.h"
+#include "sink.h"
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class IdentityVertex : public ListedSource<T>, Sink<T>
+{
+ public:
+ void process (ProcessContext<T> const & c) { ListedSource<T>::output(c); }
+ void process (ProcessContext<T> & c) { ListedSource<T>::output(c); }
+};
+
+
+} // namespace
+
+#endif // AUDIOGRAPHER_IDENTITY_VERTEX_H
diff --git a/libs/audiographer/audiographer/interleaver-inl.h b/libs/audiographer/audiographer/interleaver-inl.h
new file mode 100644
index 0000000000..07e93b2a85
--- /dev/null
+++ b/libs/audiographer/audiographer/interleaver-inl.h
@@ -0,0 +1,92 @@
+template<typename T>
+Interleaver<T>::Interleaver()
+ : channels (0)
+ , max_frames (0)
+ , buffer (0)
+{}
+
+template<typename T>
+void
+Interleaver<T>::init (unsigned int num_channels, nframes_t max_frames_per_channel)
+{
+ reset();
+ channels = num_channels;
+ max_frames = max_frames_per_channel;
+
+ buffer = new T[channels * max_frames];
+
+ for (unsigned int i = 0; i < channels; ++i) {
+ inputs.push_back (InputPtr (new Input (*this, i)));
+ }
+}
+
+template<typename T>
+typename Source<T>::SinkPtr
+Interleaver<T>::input (unsigned int channel)
+{
+ if (channel >= channels) {
+ throw Exception (*this, "Channel out of range");
+ }
+
+ return boost::static_pointer_cast<Sink<T> > (inputs[channel]);
+}
+
+template<typename T>
+void
+Interleaver<T>::reset_channels ()
+{
+ for (unsigned int i = 0; i < channels; ++i) {
+ inputs[i]->reset();
+ }
+
+}
+
+template<typename T>
+void
+Interleaver<T>::reset ()
+{
+ inputs.clear();
+ delete [] buffer;
+ buffer = 0;
+ channels = 0;
+ max_frames = 0;
+}
+
+template<typename T>
+void
+Interleaver<T>::write_channel (ProcessContext<T> const & c, unsigned int channel)
+{
+ if (c.frames() > max_frames) {
+ reset_channels();
+ throw Exception (*this, "Too many frames given to an input");
+ }
+
+ for (unsigned int i = 0; i < c.frames(); ++i) {
+ buffer[channel + (channels * i)] = c.data()[i];
+ }
+
+ nframes_t const ready_frames = ready_to_output();
+ if (ready_frames) {
+ ProcessContext<T> c_out (c, buffer, ready_frames, channels);
+ ListedSource<T>::output (c_out);
+ reset_channels ();
+ }
+}
+
+template<typename T>
+nframes_t
+Interleaver<T>::ready_to_output ()
+{
+ nframes_t ready_frames = inputs[0]->frames();
+ if (!ready_frames) { return 0; }
+
+ for (unsigned int i = 1; i < channels; ++i) {
+ nframes_t const frames = inputs[i]->frames();
+ if (!frames) { return 0; }
+ if (frames != ready_frames) {
+ init (channels, max_frames);
+ throw Exception (*this, "Frames count out of sync");
+ }
+ }
+ return ready_frames * channels;
+}
diff --git a/libs/audiographer/audiographer/interleaver.h b/libs/audiographer/audiographer/interleaver.h
new file mode 100644
index 0000000000..3d51fed5a5
--- /dev/null
+++ b/libs/audiographer/audiographer/interleaver.h
@@ -0,0 +1,71 @@
+#ifndef AUDIOGRAPHER_INTERLEAVER_H
+#define AUDIOGRAPHER_INTERLEAVER_H
+
+#include "types.h"
+#include "listed_source.h"
+#include "sink.h"
+#include "exception.h"
+
+#include <vector>
+#include <cmath>
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class Interleaver : public ListedSource<T>
+{
+ public:
+
+ Interleaver();
+ ~Interleaver() { reset(); }
+
+ void init (unsigned int num_channels, nframes_t max_frames_per_channel);
+ typename Source<T>::SinkPtr input (unsigned int channel);
+
+ private:
+
+ class Input : public Sink<T>
+ {
+ public:
+ Input (Interleaver & parent, unsigned int channel)
+ : frames_written (0), parent (parent), channel (channel) {}
+
+ void process (ProcessContext<T> const & c)
+ {
+ if (c.channels() > 1) { throw Exception (*this, "Data input has more than on channel"); }
+ if (frames_written) { throw Exception (*this, "Input channels out of sync"); }
+ frames_written = c.frames();
+ parent.write_channel (c, channel);
+ }
+
+ using Sink<T>::process;
+
+ nframes_t frames() { return frames_written; }
+ void reset() { frames_written = 0; }
+
+ private:
+ nframes_t frames_written;
+ Interleaver & parent;
+ unsigned int channel;
+ };
+
+ void reset ();
+ void reset_channels ();
+ void write_channel (ProcessContext<T> const & c, unsigned int channel);
+ nframes_t ready_to_output();
+ void output();
+
+ typedef boost::shared_ptr<Input> InputPtr;
+ std::vector<InputPtr> inputs;
+
+ unsigned int channels;
+ nframes_t max_frames;
+ T * buffer;
+};
+
+#include "interleaver-inl.h"
+
+} // namespace
+
+#endif // AUDIOGRAPHER_INTERLEAVER_H
diff --git a/libs/audiographer/audiographer/listed_source.h b/libs/audiographer/audiographer/listed_source.h
new file mode 100644
index 0000000000..bc8f144d84
--- /dev/null
+++ b/libs/audiographer/audiographer/listed_source.h
@@ -0,0 +1,50 @@
+#ifndef AUDIOGRAPHER_LISTED_SOURCE_H
+#define AUDIOGRAPHER_LISTED_SOURCE_H
+
+#include "types.h"
+#include "source.h"
+
+#include <list>
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class ListedSource : public Source<T>
+{
+ public:
+ void add_output (typename Source<T>::SinkPtr output) { outputs.push_back(output); }
+ void clear_outputs () { outputs.clear(); }
+ void remove_output (typename Source<T>::SinkPtr output) { outputs.remove(output); }
+
+ protected:
+
+ typedef std::list<typename Source<T>::SinkPtr> SinkList;
+
+ /// Helper for derived classes
+ void output (ProcessContext<T> const & c)
+ {
+ for (typename SinkList::iterator i = outputs.begin(); i != outputs.end(); ++i) {
+ (*i)->process (c);
+ }
+ }
+
+ void output (ProcessContext<T> & c)
+ {
+ if (output_size_is_one()) {
+ // only one output, so we can keep this non-const
+ outputs.front()->process (c);
+ } else {
+ output (const_cast<ProcessContext<T> const &> (c));
+ }
+ }
+
+ inline bool output_size_is_one () { return (!outputs.empty() && ++outputs.begin() == outputs.end()); }
+
+ SinkList outputs;
+};
+
+} // namespace
+
+#endif //AUDIOGRAPHER_LISTED_SOURCE_H
+
diff --git a/libs/audiographer/audiographer/normalizer.h b/libs/audiographer/audiographer/normalizer.h
new file mode 100644
index 0000000000..dcaac75568
--- /dev/null
+++ b/libs/audiographer/audiographer/normalizer.h
@@ -0,0 +1,82 @@
+#ifndef AUDIOGRAPHER_NORMALIZER_H
+#define AUDIOGRAPHER_NORMALIZER_H
+
+#include "listed_source.h"
+#include "sink.h"
+#include "routines.h"
+
+#include <cstring>
+
+namespace AudioGrapher
+{
+
+class Normalizer : public ListedSource<float>, Sink<float>
+{
+ public:
+ Normalizer (float target_dB)
+ : enabled (false)
+ , buffer (0)
+ , buffer_size (0)
+ {
+ target = pow (10.0f, target_dB * 0.05f);
+ }
+
+ ~Normalizer()
+ {
+ delete [] buffer;
+ }
+
+ void set_peak (float peak)
+ {
+ if (peak == 0.0f || peak == target) {
+ /* don't even try */
+ enabled = false;
+ } else {
+ enabled = true;
+ gain = target / peak;
+ }
+ }
+
+ void alloc_buffer(nframes_t frames)
+ {
+ delete [] buffer;
+ buffer = new float[frames];
+ buffer_size = frames;
+ }
+
+ void process (ProcessContext<float> const & c)
+ {
+ if (c.frames() > buffer_size) {
+ throw Exception (*this, "Too many frames given to process()");
+ }
+
+ if (enabled) {
+ memcpy (buffer, c.data(), c.frames() * sizeof(float));
+ Routines::apply_gain_to_buffer (buffer, c.frames(), gain);
+ }
+
+ ProcessContext<float> c_out (c, buffer);
+ ListedSource<float>::output (c_out);
+ }
+
+ void process (ProcessContext<float> & c)
+ {
+ if (enabled) {
+ Routines::apply_gain_to_buffer (c.data(), c.frames(), gain);
+ }
+ ListedSource<float>::output(c);
+ }
+
+ private:
+ bool enabled;
+ float target;
+ float gain;
+
+ float * buffer;
+ nframes_t buffer_size;
+};
+
+
+} // namespace
+
+#endif // AUDIOGRAPHER_NORMALIZER_H
diff --git a/libs/audiographer/audiographer/peak_reader.h b/libs/audiographer/audiographer/peak_reader.h
new file mode 100644
index 0000000000..e5aaf7081c
--- /dev/null
+++ b/libs/audiographer/audiographer/peak_reader.h
@@ -0,0 +1,38 @@
+#ifndef AUDIOGRAPHER_PEAK_READER_H
+#define AUDIOGRAPHER_PEAK_READER_H
+
+#include "listed_source.h"
+#include "sink.h"
+#include "routines.h"
+
+namespace AudioGrapher
+{
+
+class PeakReader : public ListedSource<float>, public Sink<float>
+{
+ public:
+ PeakReader() : peak (0.0) {}
+
+ float get_peak() { return peak; }
+ void reset() { peak = 0.0; }
+
+ void process (ProcessContext<float> const & c)
+ {
+ peak = Routines::compute_peak (c.data(), c.frames(), peak);
+ ListedSource<float>::output(c);
+ }
+
+ void process (ProcessContext<float> & c)
+ {
+ peak = Routines::compute_peak (c.data(), c.frames(), peak);
+ ListedSource<float>::output(c);
+ }
+
+ private:
+ float peak;
+};
+
+
+} // namespace
+
+#endif // AUDIOGRAPHER_PEAK_READER_H
diff --git a/libs/audiographer/audiographer/process_context.h b/libs/audiographer/audiographer/process_context.h
new file mode 100644
index 0000000000..080e492944
--- /dev/null
+++ b/libs/audiographer/audiographer/process_context.h
@@ -0,0 +1,154 @@
+#ifndef AUDIOGRAPHER_PROCESS_CONTEXT_H
+#define AUDIOGRAPHER_PROCESS_CONTEXT_H
+
+#include "types.h"
+
+#include <cstring>
+
+namespace AudioGrapher
+{
+
+/**
+ * Processing context. Constness only applies to data, not flags
+ */
+
+template <typename T>
+class ProcessContext {
+
+public:
+
+ typedef FlagField::Flag Flag;
+
+ enum Flags {
+ EndOfInput = 0
+ };
+
+public:
+
+ /// Basic constructor with data, frame and channel count
+ ProcessContext (T * data, nframes_t frames, ChannelCount channels)
+ : _data (data), _frames (frames), _channels (channels) {}
+
+ /// Normal copy constructor
+ ProcessContext (ProcessContext<T> const & other)
+ : _data (other._data), _frames (other._frames), _channels (other._channels), _flags (other._flags) {}
+
+ /// "Copy constructor" with unique data, frame and channel count, but copies flags
+ template<typename Y>
+ ProcessContext (ProcessContext<Y> const & other, T * data, nframes_t frames, ChannelCount channels)
+ : _data (data), _frames (frames), _channels (channels), _flags (other.flags()) {}
+
+ /// "Copy constructor" with unique data and frame count, but copies channel count and flags
+ template<typename Y>
+ ProcessContext (ProcessContext<Y> const & other, T * data, nframes_t frames)
+ : _data (data), _frames (frames), _channels (other.channels()), _flags (other.flags()) {}
+
+ /// "Copy constructor" with unique data, but copies frame and channel count + flags
+ template<typename Y>
+ ProcessContext (ProcessContext<Y> const & other, T * data)
+ : _data (data), _frames (other.frames()), _channels (other.channels()), _flags (other.flags()) {}
+
+ virtual ~ProcessContext () {}
+
+ /// \a data points to the array of data to process
+ inline T const * data() const { return _data; }
+ inline T * data() { return _data; }
+
+ /// \a frames tells how many frames the array pointed by data contains
+ inline nframes_t const & frames() const { return _frames; }
+ inline nframes_t & frames() { return _frames; }
+
+ /** \a channels tells how many interleaved channels \a data contains
+ * If \a channels is greater than 1, each channel contains \a frames / \a channels frames of data
+ */
+ inline ChannelCount const & channels() const { return _channels; }
+ inline ChannelCount & channels() { return _channels; }
+
+ /// Returns the amount of frames per channel
+ inline nframes_t frames_per_channel() const { return _frames / _channels; }
+
+ /* Flags */
+
+ inline bool has_flag (Flag flag) const { return _flags.has (flag); }
+ inline void set_flag (Flag flag) const { _flags.set (flag); }
+ inline void remove_flag (Flag flag) const { _flags.remove (flag); }
+ inline FlagField const & flags () const { return _flags; }
+
+protected:
+ T * const _data;
+ nframes_t _frames;
+ ChannelCount _channels;
+
+ mutable FlagField _flags;
+};
+
+/// A process context that allocates and owns it's data buffer
+template <typename T>
+struct AllocatingProcessContext : public ProcessContext<T>
+{
+ /// Allocates uninitialized memory
+ AllocatingProcessContext (nframes_t frames, ChannelCount channels)
+ : ProcessContext<T> (new T[frames], frames, channels) {}
+
+ /// Copy constructor, copies data from other ProcessContext
+ AllocatingProcessContext (ProcessContext<T> const & other)
+ : ProcessContext<T> (other, new T[other._frames])
+ { memcpy (ProcessContext<T>::_data, other._data, other._channels * other._frames * sizeof (T)); }
+
+ /// "Copy constructor" with uninitialized data, unique frame and channel count, but copies flags
+ template<typename Y>
+ AllocatingProcessContext (ProcessContext<Y> const & other, nframes_t frames, ChannelCount channels)
+ : ProcessContext<T> (other, new T[frames], frames, channels) {}
+
+ /// "Copy constructor" with uninitialized data, unique frame count, but copies channel count and flags
+ template<typename Y>
+ AllocatingProcessContext (ProcessContext<Y> const & other, nframes_t frames)
+ : ProcessContext<T> (other, new T[frames], frames, other.channels()) {}
+
+ /// "Copy constructor" uninitialized data, that copies frame and channel count + flags
+ template<typename Y>
+ AllocatingProcessContext (ProcessContext<Y> const & other)
+ : ProcessContext<T> (other, new T[other._frames]) {}
+
+ ~AllocatingProcessContext () { delete [] ProcessContext<T>::_data; }
+};
+
+/// A wrapper for a const ProcesContext which can be created from const data
+template <typename T>
+class ConstProcessContext
+{
+ public:
+ /// Basic constructor with data, frame and channel count
+ ConstProcessContext (T const * data, nframes_t frames, ChannelCount channels)
+ : context (const_cast<T *>(data), frames, channels) {}
+
+ /// Copy constructor from const ProcessContext
+ ConstProcessContext (ProcessContext<T> const & other)
+ : context (const_cast<ProcessContext<T> &> (other)) {}
+
+ /// "Copy constructor", with unique data, frame and channel count, but copies flags
+ template<typename ProcessContext>
+ ConstProcessContext (ProcessContext const & other, T const * data, nframes_t frames, ChannelCount channels)
+ : context (other, const_cast<T *>(data), frames, channels) {}
+
+ /// "Copy constructor", with unique data and frame count, but copies channel count and flags
+ template<typename ProcessContext>
+ ConstProcessContext (ProcessContext const & other, T const * data, nframes_t frames)
+ : context (other, const_cast<T *>(data), frames) {}
+
+ /// "Copy constructor", with unique data, but copies frame and channel count + flags
+ template<typename ProcessContext>
+ ConstProcessContext (ProcessContext const & other, T const * data)
+ : context (other, const_cast<T *>(data)) {}
+
+ inline operator ProcessContext<T> const & () { return context; }
+ inline ProcessContext<T> const & operator() () { return context; }
+ inline ProcessContext<T> const * operator& () { return &context; }
+
+ private:
+ ProcessContext<T> const context;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_PROCESS_CONTEXT_H
diff --git a/libs/audiographer/audiographer/routines.h b/libs/audiographer/audiographer/routines.h
new file mode 100644
index 0000000000..9ae6b7a255
--- /dev/null
+++ b/libs/audiographer/audiographer/routines.h
@@ -0,0 +1,53 @@
+#ifndef AUDIOGRAPHER_ROUTINES_H
+#define AUDIOGRAPHER_ROUTINES_H
+
+#include "types.h"
+
+#include <cmath>
+
+namespace AudioGrapher
+{
+
+class Routines
+{
+ public:
+ typedef float (*compute_peak_t) (float const *, nframes_t, float);
+ typedef void (*apply_gain_to_buffer_t) (float *, nframes_t, float);
+
+ static void override_compute_peak (compute_peak_t func) { _compute_peak = func; }
+ static void override_apply_gain_to_buffer (apply_gain_to_buffer_t func) { _apply_gain_to_buffer = func; }
+
+ static inline float compute_peak (float const * data, nframes_t frames, float current_peak)
+ {
+ return (*_compute_peak) (data, frames, current_peak);
+ }
+
+ static inline void apply_gain_to_buffer (float * data, nframes_t frames, float gain)
+ {
+ (*_apply_gain_to_buffer) (data, frames, gain);
+ }
+
+ private:
+ static inline float default_compute_peak (float const * data, nframes_t frames, float current_peak)
+ {
+ for (nframes_t i = 0; i < frames; ++i) {
+ float abs = std::fabs(data[i]);
+ if (abs > current_peak) { current_peak = abs; }
+ }
+ return current_peak;
+ }
+
+ static inline void default_apply_gain_to_buffer (float * data, nframes_t frames, float gain)
+ {
+ for (nframes_t i = 0; i < frames; ++i) {
+ data[i] *= gain;
+ }
+ }
+
+ static compute_peak_t _compute_peak;
+ static apply_gain_to_buffer_t _apply_gain_to_buffer;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_ROUTINES_H
diff --git a/libs/audiographer/audiographer/sample_format_converter.h b/libs/audiographer/audiographer/sample_format_converter.h
new file mode 100644
index 0000000000..12976ffdd2
--- /dev/null
+++ b/libs/audiographer/audiographer/sample_format_converter.h
@@ -0,0 +1,67 @@
+#ifndef AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H
+#define AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H
+
+#include "listed_source.h"
+#include "sink.h"
+#include "gdither/gdither_types.h"
+
+namespace AudioGrapher
+{
+
+/// Dither types from the gdither library
+enum DitherType
+{
+ D_None = GDitherNone, ///< No didtering
+ D_Rect = GDitherRect, ///< Rectangular dithering, i.e. white noise
+ D_Tri = GDitherTri, ///< Triangular dithering
+ D_Shaped = GDitherShaped ///< Actually noise shaping, only works for 46kHzish signals
+};
+
+/** Sample format converter that does dithering.
+ * This class can only convert floats to either \a float, \a int32_t, \a int16_t, or \a uint8_t
+ */
+template <typename TOut>
+class SampleFormatConverter : public Sink<float>, public ListedSource<TOut>
+{
+ public:
+ /** Constructor
+ * \param channels number of channels in stream
+ */
+ SampleFormatConverter (uint32_t channels);
+ ~SampleFormatConverter ();
+
+ /** Initialize and allocate buffers for processing.
+ * \param max_frames maximum number of frames that is allowed to be used in calls to \a process()
+ * \param type dither type from \a DitherType
+ * \param data_width data with in bits
+ * \note If the non-const version of process() is used with floats,
+ * there is no need to call this function.
+ */
+ void init (nframes_t max_frames, int type, int data_width);
+
+ /// Set whether or not clipping to [-1.0, 1.0] should occur when TOut = float. Clipping is off by default
+ void set_clip_floats (bool yn) { clip_floats = yn; }
+
+ /// Processes data without modifying it
+ void process (ProcessContext<float> const & c_in);
+
+ /// This version is only different in the case when \a TOut = float, and float clipping is on.
+ void process (ProcessContext<float> & c_in);
+
+ private:
+ void reset();
+ void init_common(nframes_t max_frames); // not-template-specialized part of init
+ void check_frame_count(nframes_t frames);
+
+ uint32_t channels;
+ GDither dither;
+ nframes_t data_out_size;
+ TOut * data_out;
+
+ bool clip_floats;
+
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SAMPLE_FORMAT_CONVERTER_H
diff --git a/libs/audiographer/audiographer/silence_trimmer.h b/libs/audiographer/audiographer/silence_trimmer.h
new file mode 100644
index 0000000000..46190cfeae
--- /dev/null
+++ b/libs/audiographer/audiographer/silence_trimmer.h
@@ -0,0 +1,191 @@
+#ifndef AUDIOGRAPHER_SILENCE_TRIMMER_H
+#define AUDIOGRAPHER_SILENCE_TRIMMER_H
+
+#include "listed_source.h"
+#include "sink.h"
+#include "exception.h"
+#include "utils.h"
+
+#include <cstring>
+
+namespace AudioGrapher {
+
+template<typename T>
+class SilenceTrimmer : public ListedSource<T>, public Sink<T>
+{
+ public:
+
+ SilenceTrimmer()
+ {
+ reset ();
+ }
+
+ void reset()
+ {
+ in_beginning = true;
+ in_end = false;
+ trim_beginning = false;
+ trim_end = false;
+ silence_frames = 0;
+ max_output_frames = 0;
+ add_to_beginning = 0;
+ add_to_end = 0;
+ }
+
+ void add_silence_to_beginning (nframes_t frames_per_channel)
+ {
+ if (!in_beginning) {
+ throw Exception(*this, "Tried to add silence to beginning after already outputting data");
+ }
+ add_to_beginning = frames_per_channel;
+ }
+
+ void add_silence_to_end (nframes_t frames_per_channel)
+ {
+ if (in_end) {
+ throw Exception(*this, "Tried to add silence to end after already reaching end");
+ }
+ add_to_end = frames_per_channel;
+ }
+
+ void set_trim_beginning (bool yn)
+ {
+ if (!in_beginning) {
+ throw Exception(*this, "Tried to set beginning trim after already outputting data");
+ }
+ trim_beginning = yn;
+ }
+
+ void set_trim_end (bool yn)
+ {
+ if (in_end) {
+ throw Exception(*this, "Tried to set end trim after already reaching end");
+ }
+ trim_end = yn;
+ }
+
+ void limit_output_size (nframes_t max_frames)
+ {
+ max_output_frames = max_frames;
+ }
+
+ void process (ProcessContext<T> const & c)
+ {
+ if (in_end) { throw Exception(*this, "process() after reacing end of input"); }
+ in_end = c.has_flag (ProcessContext<T>::EndOfInput);
+
+ nframes_t frame_index = 0;
+
+ if (in_beginning) {
+
+ bool has_data = true;
+
+ // only check silence if doing either of these
+ // This will set both has_data and frame_index
+ if (add_to_beginning || trim_beginning) {
+ has_data = find_first_non_zero_sample (c, frame_index);
+ }
+
+ // Added silence if there is silence to add
+ if (add_to_beginning) {
+ ConstProcessContext<T> c_copy (c);
+ if (has_data) { // There will be more output, so remove flag
+ c_copy().remove_flag (ProcessContext<T>::EndOfInput);
+ }
+ add_to_beginning *= c.channels();
+ output_silence_frames (c_copy, add_to_beginning);
+ }
+
+ // If we are not trimming the beginning, output everything
+ // Then has_data = true and frame_index = 0
+ // Otherwise these reflect the silence state
+ if (has_data) {
+ in_beginning = false;
+ ConstProcessContext<T> c_out (c, &c.data()[frame_index], c.frames() - frame_index);
+ ListedSource<T>::output (c_out);
+ }
+
+ } else if (trim_end) { // Only check zero samples if trimming end
+
+ if (find_first_non_zero_sample (c, frame_index)) {
+ // context contains non-zero data
+ output_silence_frames (c, silence_frames); // flush intermediate silence
+ ListedSource<T>::output (c); // output rest of data
+ } else { // whole context is zero
+ silence_frames += c.frames();
+ }
+
+ } else { // no need to do anything special
+
+ ListedSource<T>::output (c);
+ }
+
+ // Finally if in end, add silence to end
+ if (in_end && add_to_end) {
+ add_to_end *= c.channels();
+ output_silence_frames (c, add_to_end, true);
+ }
+ }
+
+ using Sink<T>::process;
+
+ private:
+
+ bool find_first_non_zero_sample (ProcessContext<T> const & c, nframes_t & result_frame)
+ {
+ for (nframes_t i = 0; i < c.frames(); ++i) {
+ if (c.data()[i] != static_cast<T>(0.0)) {
+ result_frame = i;
+ // Round down to nearest interleaved "frame" beginning
+ result_frame -= result_frame % c.channels();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void output_silence_frames (ProcessContext<T> const & c, nframes_t & total_frames, bool adding_to_end = false)
+ {
+ nframes_t silence_buffer_size = Utils::get_zero_buffer_size<T>();
+ if (silence_buffer_size == 0) { throw Exception (*this, "Utils::init_zeros has not been called!"); }
+
+ bool end_of_input = c.has_flag (ProcessContext<T>::EndOfInput);
+ c.remove_flag (ProcessContext<T>::EndOfInput);
+
+ while (total_frames > 0) {
+ nframes_t frames = std::min (silence_buffer_size, total_frames);
+ if (max_output_frames) {
+ frames = std::min (frames, max_output_frames);
+ }
+ frames -= frames % c.channels();
+
+ total_frames -= frames;
+ ConstProcessContext<T> c_out (c, Utils::get_zeros<T>(frames), frames);
+
+ // boolean commentation :)
+ bool const no_more_silence_will_be_added = adding_to_end || (add_to_end == 0);
+ bool const is_last_frame_output_in_this_function = (total_frames == 0);
+ if (end_of_input && no_more_silence_will_be_added && is_last_frame_output_in_this_function) {
+ c_out().set_flag (ProcessContext<T>::EndOfInput);
+ }
+ ListedSource<T>::output (c_out);
+ }
+ }
+
+
+ bool in_beginning;
+ bool in_end;
+
+ bool trim_beginning;
+ bool trim_end;
+
+ nframes_t silence_frames;
+ nframes_t max_output_frames;
+
+ nframes_t add_to_beginning;
+ nframes_t add_to_end;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SILENCE_TRIMMER_H
diff --git a/libs/audiographer/audiographer/sink.h b/libs/audiographer/audiographer/sink.h
new file mode 100644
index 0000000000..486fccfd13
--- /dev/null
+++ b/libs/audiographer/audiographer/sink.h
@@ -0,0 +1,42 @@
+#ifndef AUDIOGRAPHER_SINK_H
+#define AUDIOGRAPHER_SINK_H
+
+#include <boost/shared_ptr.hpp>
+
+#include "process_context.h"
+
+namespace AudioGrapher
+{
+
+template <typename T>
+class Sink {
+ public:
+ virtual ~Sink () {}
+
+ /** Process given data.
+ * The data can not be modified, so in-place processing is not allowed.
+ * At least this function must be implemented by deriving classes
+ */
+ virtual void process (ProcessContext<T> const & context) = 0;
+
+ /** Process given data
+ * Data may be modified, so in place processing is allowed.
+ * The default implementation calls the non-modifying version,
+ * so this function does not need to be overriden.
+ * However, if the sink can do in-place processing,
+ * overriding this is highly recommended.
+ *
+ * If this is not overridden adding "using Sink<T>::process;"
+ * to the deriving class declaration is suggested to avoid
+ * warnings about hidden virtual functions.
+ */
+ inline virtual void process (ProcessContext<T> & context)
+ {
+ this->process (static_cast<ProcessContext<T> const &> (context));
+ }
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SINK_H
+
diff --git a/libs/audiographer/audiographer/sndfile_base.h b/libs/audiographer/audiographer/sndfile_base.h
new file mode 100644
index 0000000000..fd6c5f3552
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_base.h
@@ -0,0 +1,30 @@
+#ifndef AUDIOGRAPHER_SNDFILE_BASE_H
+#define AUDIOGRAPHER_SNDFILE_BASE_H
+
+#include <string>
+#include <sndfile.h>
+#include <sigc++/signal.h>
+
+#include "types.h"
+
+namespace AudioGrapher {
+
+/// Common interface for templated libsndfile readers/writers
+class SndfileBase
+{
+ public:
+
+ sigc::signal<void, std::string> FileWritten;
+
+ protected:
+ SndfileBase (ChannelCount channels, nframes_t samplerate, int format, std::string const & path);
+ virtual ~SndfileBase ();
+
+ std::string path;
+ SF_INFO sf_info;
+ SNDFILE * sndfile;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SNDFILE_BASE_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/sndfile_reader.h b/libs/audiographer/audiographer/sndfile_reader.h
new file mode 100644
index 0000000000..9e47da56b2
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_reader.h
@@ -0,0 +1,40 @@
+#ifndef AUDIOGRAPHER_SNDFILE_READER_H
+#define AUDIOGRAPHER_SNDFILE_READER_H
+
+#include "sndfile_base.h"
+#include "listed_source.h"
+#include "process_context.h"
+
+namespace AudioGrapher
+{
+
+/** Reader for audio files using libsndfile.
+ * Once again only short, int and float are valid template parameters
+ */
+template<typename T>
+class SndfileReader : public virtual SndfileBase, public ListedSource<T>
+{
+ public:
+
+ enum SeekType {
+ SeekBeginning = SEEK_SET, //< Seek from beginning of file
+ SeekCurrent = SEEK_CUR, //< Seek from current position
+ SeekEnd = SEEK_END //< Seek from end
+ };
+
+ public:
+
+ SndfileReader (ChannelCount channels, nframes_t samplerate, int format, std::string path);
+
+ nframes_t seek (nframes_t frames, SeekType whence);
+ nframes_t read (ProcessContext<T> & context);
+
+ private:
+
+ void init(); // init read function
+ sf_count_t (*read_func)(SNDFILE *, T *, sf_count_t);
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SNDFILE_READER_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/sndfile_writer.h b/libs/audiographer/audiographer/sndfile_writer.h
new file mode 100644
index 0000000000..a92da982c1
--- /dev/null
+++ b/libs/audiographer/audiographer/sndfile_writer.h
@@ -0,0 +1,29 @@
+#ifndef AUDIOGRAPHER_SNDFILE_WRITER_H
+#define AUDIOGRAPHER_SNDFILE_WRITER_H
+
+#include "sndfile_base.h"
+#include "types.h"
+#include "sink.h"
+
+namespace AudioGrapher
+{
+
+/// Template parameter specific parts of sndfile writer
+template <typename T>
+class SndfileWriter : public virtual SndfileBase, public Sink<T>
+{
+ public:
+ SndfileWriter (ChannelCount channels, nframes_t samplerate, int format, std::string const & path);
+
+ void process (ProcessContext<T> const & c);
+ using Sink<T>::process;
+
+ private:
+
+ void init (); // Inits write function
+ sf_count_t (*write_func)(SNDFILE *, const T *, sf_count_t);
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SNDFILE_WRITER_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/source.h b/libs/audiographer/audiographer/source.h
new file mode 100644
index 0000000000..8ffad204ba
--- /dev/null
+++ b/libs/audiographer/audiographer/source.h
@@ -0,0 +1,28 @@
+#ifndef AUDIOGRAPHER_SOURCE_H
+#define AUDIOGRAPHER_SOURCE_H
+
+#include "types.h"
+#include "sink.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class Source
+{
+ public:
+ virtual ~Source () { }
+
+ typedef boost::shared_ptr<Sink<T> > SinkPtr;
+
+ virtual void add_output (SinkPtr output) = 0;
+ virtual void clear_outputs () = 0;
+ virtual void remove_output (SinkPtr output) = 0;
+};
+
+} // namespace
+
+#endif //AUDIOGRAPHER_SOURCE_H
+
diff --git a/libs/audiographer/audiographer/sr_converter.h b/libs/audiographer/audiographer/sr_converter.h
new file mode 100644
index 0000000000..073fdc8247
--- /dev/null
+++ b/libs/audiographer/audiographer/sr_converter.h
@@ -0,0 +1,51 @@
+#ifndef AUDIOGRAPHER_SR_CONVERTER_H
+#define AUDIOGRAPHER_SR_CONVERTER_H
+
+#include <samplerate.h>
+
+#include "types.h"
+#include "listed_source.h"
+#include "sink.h"
+
+namespace AudioGrapher
+{
+
+class SampleRateConverter : public ListedSource<float>, public Sink<float>
+{
+ public:
+ SampleRateConverter (uint32_t channels);
+ ~SampleRateConverter ();
+
+ // not RT safe
+ void init (nframes_t in_rate, nframes_t out_rate, int quality = 0);
+
+ // returns max amount of frames that will be output
+ nframes_t allocate_buffers (nframes_t max_frames);
+
+ // could be RT safe (check libsamplerate to be sure)
+ void process (ProcessContext<float> const & c);
+ using Sink<float>::process;
+
+ private:
+
+ void set_end_of_input (ProcessContext<float> const & c);
+ void reset ();
+
+ bool active;
+ uint32_t channels;
+ nframes_t max_frames_in;
+
+ float * leftover_data;
+ nframes_t leftover_frames;
+ nframes_t max_leftover_frames;
+
+ float * data_out;
+ nframes_t data_out_size;
+
+ SRC_DATA src_data;
+ SRC_STATE* src_state;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_SR_CONVERTER_H
diff --git a/libs/audiographer/audiographer/threader.h b/libs/audiographer/audiographer/threader.h
new file mode 100644
index 0000000000..e6c3aa97bf
--- /dev/null
+++ b/libs/audiographer/audiographer/threader.h
@@ -0,0 +1,120 @@
+#ifndef AUDIOGRAPHER_THREADER_H
+#define AUDIOGRAPHER_THREADER_H
+
+#include <glibmm/threadpool.h>
+#include <sigc++/slot.h>
+#include <boost/format.hpp>
+
+#include <glib.h>
+#include <vector>
+#include <algorithm>
+
+#include "source.h"
+#include "sink.h"
+#include "exception.h"
+
+namespace AudioGrapher
+{
+
+class ThreaderException : public Exception
+{
+ public:
+ template<typename T>
+ ThreaderException (T const & thrower, std::exception const & e)
+ : Exception (thrower,
+ boost::str ( boost::format
+ ("\n\t- Dynamic type: %1%\n\t- what(): %2%") % name (e) % e.what() ))
+ { }
+};
+
+template <typename T>
+class Threader : public Source<T>, public Sink<T>
+{
+ private:
+ typedef std::vector<typename Source<T>::SinkPtr> OutputVec;
+
+ public:
+
+ Threader (Glib::ThreadPool & thread_pool, long wait_timeout_milliseconds = 1000)
+ : thread_pool (thread_pool)
+ , readers (0)
+ , wait_timeout (wait_timeout_milliseconds)
+ { }
+
+ virtual ~Threader () {}
+
+ void add_output (typename Source<T>::SinkPtr output) { outputs.push_back (output); }
+ void clear_outputs () { outputs.clear (); }
+ void remove_output (typename Source<T>::SinkPtr output) {
+ typename OutputVec::iterator new_end = std::remove(outputs.begin(), outputs.end(), output);
+ outputs.erase (new_end, outputs.end());
+ }
+
+ /* The context has to be const, because this is working concurrently */
+ void process (ProcessContext<T> const & c)
+ {
+ wait_mutex.lock();
+
+ exception.reset();
+
+ unsigned int outs = outputs.size();
+ g_atomic_int_add (&readers, outs);
+ for (unsigned int i = 0; i < outs; ++i) {
+ thread_pool.push (sigc::bind (sigc::mem_fun (this, &Threader::process_output), c, i));
+ }
+
+ wait();
+ }
+
+ using Sink<T>::process;
+
+ private:
+
+ void wait()
+ {
+ Glib::TimeVal wait_time;
+ wait_time.assign_current_time();
+ wait_time.add_milliseconds(wait_timeout);
+
+ wait_cond.timed_wait(wait_mutex, wait_time);
+ bool timed_out = (g_atomic_int_get (&readers) != 0);
+ wait_mutex.unlock();
+ if (timed_out) { throw Exception (*this, "wait timed out"); }
+
+ if (exception) {
+ throw *exception;
+ }
+ }
+
+ void process_output(ProcessContext<T> const & c, unsigned int output)
+ {
+ try {
+ outputs[output]->process (c);
+ } catch (std::exception const & e) {
+ // Only first exception will be passed on
+ exception_mutex.lock();
+ if(!exception) { exception.reset (new ThreaderException (*this, e)); }
+ exception_mutex.unlock();
+ }
+
+ if (g_atomic_int_dec_and_test (&readers)) {
+ wait_cond.signal();
+ }
+ }
+
+ OutputVec outputs;
+
+ Glib::ThreadPool & thread_pool;
+ Glib::Mutex wait_mutex;
+ Glib::Cond wait_cond;
+ gint readers;
+ long wait_timeout;
+
+ Glib::Mutex exception_mutex;
+ boost::shared_ptr<ThreaderException> exception;
+
+};
+
+} // namespace
+
+#endif //AUDIOGRAPHER_THREADER_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/tmp_file.h b/libs/audiographer/audiographer/tmp_file.h
new file mode 100644
index 0000000000..a5537e3a69
--- /dev/null
+++ b/libs/audiographer/audiographer/tmp_file.h
@@ -0,0 +1,25 @@
+#ifndef AUDIOGRAPHER_TMP_FILE_H
+#define AUDIOGRAPHER_TMP_FILE_H
+
+#include "sndfile_writer.h"
+#include "sndfile_reader.h"
+
+namespace AudioGrapher
+{
+
+template<typename T>
+class TmpFile : public SndfileWriter<T>, public SndfileReader<T>
+{
+ public:
+
+ TmpFile (ChannelCount channels, nframes_t samplerate, int format)
+ : SndfileBase (channels, samplerate, format, "temp")
+ , SndfileWriter<T> (channels, samplerate, format, "temp")
+ , SndfileReader<T> (channels, samplerate, format, "temp")
+ {}
+
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_TMP_FILE_H \ No newline at end of file
diff --git a/libs/audiographer/audiographer/types.h b/libs/audiographer/audiographer/types.h
new file mode 100644
index 0000000000..f48f8f807a
--- /dev/null
+++ b/libs/audiographer/audiographer/types.h
@@ -0,0 +1,32 @@
+#ifndef AUDIOGRAPHER_TYPES_H
+#define AUDIOGRAPHER_TYPES_H
+
+#include <stdint.h>
+
+namespace AudioGrapher {
+
+typedef int64_t nframes_t;
+typedef uint8_t ChannelCount;
+
+/** Flag field capable of holding 32 flags.
+ Easily grown in size to 64 flags by changing storage_type */
+class FlagField {
+ public:
+ typedef uint8_t Flag;
+ typedef uint32_t storage_type;
+
+ FlagField() : _flags (0) {}
+ FlagField(FlagField const & other) : _flags (other._flags) {}
+
+ inline bool has (Flag flag) const { return _flags & (1 << flag); }
+ inline void set (Flag flag) { _flags |= (1 << flag); }
+ inline void remove (Flag flag) { _flags &= ~(1 << flag); }
+ inline storage_type flags () const { return _flags; }
+
+ private:
+ storage_type _flags;
+};
+
+} // namespace
+
+#endif // __audiographer_types_h__ \ No newline at end of file
diff --git a/libs/audiographer/audiographer/utils.h b/libs/audiographer/audiographer/utils.h
new file mode 100644
index 0000000000..2f9c51918b
--- /dev/null
+++ b/libs/audiographer/audiographer/utils.h
@@ -0,0 +1,59 @@
+#ifndef AUDIOGRAPHER_UTILS_H
+#define AUDIOGRAPHER_UTILS_H
+
+#include "types.h"
+#include "exception.h"
+
+#include <cstring>
+
+namespace AudioGrapher
+{
+
+class Utils
+{
+ public:
+
+ static void free_resources();
+
+ /// Initialize zero buffer, if buffer is != 0, it will be used as the zero buffer
+ template <typename T>
+ static void init_zeros (nframes_t frames, T const * buffer = 0)
+ {
+ if (frames == 0) {
+ throw Exception (Utils(), "init_zeros must be called with an argument greater than zero.");
+ }
+ unsigned long n_zeros = frames * sizeof (T);
+ if (n_zeros <= num_zeros) { return; }
+ delete [] zeros;
+ if (buffer) {
+ zeros = reinterpret_cast<char const *>(buffer);
+ } else {
+ zeros = new char[n_zeros];
+ memset (const_cast<char *>(zeros), 0, n_zeros);
+ }
+ num_zeros = n_zeros;
+ }
+
+ template <typename T>
+ static T const * get_zeros (nframes_t frames)
+ {
+ if (frames * sizeof (T) > num_zeros) {
+ throw Exception (Utils(), "init_zeros has not been called with a large enough frame count");
+ }
+ return reinterpret_cast<T const *> (zeros);
+ }
+
+ template <typename T>
+ static nframes_t get_zero_buffer_size ()
+ {
+ return num_zeros / sizeof (T);
+ }
+
+ private:
+ static char const * zeros;
+ static unsigned long num_zeros;
+};
+
+} // namespace
+
+#endif // AUDIOGRAPHER_ROUTINES_H
diff --git a/libs/audiographer/autowaf.py b/libs/audiographer/autowaf.py
new file mode 100644
index 0000000000..aaac8efc04
--- /dev/null
+++ b/libs/audiographer/autowaf.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Waf utilities for easily building standard unixey packages/libraries
+# Licensed under the GNU GPL v2 or later, see COPYING file for details.
+# Copyright (C) 2008 Dave Robillard
+# Copyright (C) 2008 Nedko Arnaudov
+
+import os
+import misc
+import Configure
+import Options
+import Utils
+import sys
+from TaskGen import feature, before, after
+
+global g_is_child
+g_is_child = False
+
+# Only run autowaf hooks once (even if sub projects call several times)
+global g_step
+g_step = 0
+
+# Compute dependencies globally
+#import preproc
+#preproc.go_absolute = True
+
+@feature('cc', 'cxx')
+@after('apply_lib_vars')
+@before('apply_obj_vars_cc', 'apply_obj_vars_cxx')
+def include_config_h(self):
+ self.env.append_value('INC_PATHS', self.bld.srcnode)
+
+def set_options(opt):
+ "Add standard autowaf options if they havn't been added yet"
+ global g_step
+ if g_step > 0:
+ return
+ opt.tool_options('compiler_cc')
+ opt.tool_options('compiler_cxx')
+ opt.add_option('--debug', action='store_true', default=False, dest='debug',
+ help="Build debuggable binaries [Default: False]")
+ opt.add_option('--strict', action='store_true', default=False, dest='strict',
+ help="Use strict compiler flags and show all warnings [Default: False]")
+ opt.add_option('--build-docs', action='store_true', default=False, dest='build_docs',
+ help="Build documentation - requires doxygen [Default: False]")
+ opt.add_option('--bundle', action='store_true', default=False,
+ help="Build a self-contained bundle [Default: False]")
+ opt.add_option('--bindir', type='string',
+ help="Executable programs [Default: PREFIX/bin]")
+ opt.add_option('--libdir', type='string',
+ help="Libraries [Default: PREFIX/lib]")
+ opt.add_option('--includedir', type='string',
+ help="Header files [Default: PREFIX/include]")
+ opt.add_option('--datadir', type='string',
+ help="Shared data [Default: PREFIX/share]")
+ opt.add_option('--configdir', type='string',
+ help="Configuration data [Default: PREFIX/etc]")
+ opt.add_option('--mandir', type='string',
+ help="Manual pages [Default: DATADIR/man]")
+ opt.add_option('--htmldir', type='string',
+ help="HTML documentation [Default: DATADIR/doc/PACKAGE]")
+ opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
+ help="Install LV2 bundles to user-local location [Default: False]")
+ if sys.platform == "darwin":
+ opt.add_option('--lv2dir', type='string',
+ help="LV2 bundles [Default: /Library/Audio/Plug-Ins/LV2]")
+ else:
+ opt.add_option('--lv2dir', type='string',
+ help="LV2 bundles [Default: LIBDIR/lv2]")
+ g_step = 1
+
+def check_header(conf, name, define='', mandatory=False):
+ "Check for a header iff it hasn't been checked for yet"
+ if type(conf.env['AUTOWAF_HEADERS']) != dict:
+ conf.env['AUTOWAF_HEADERS'] = {}
+
+ checked = conf.env['AUTOWAF_HEADERS']
+ if not name in checked:
+ checked[name] = True
+ if define != '':
+ conf.check(header_name=name, define_name=define, mandatory=mandatory)
+ else:
+ conf.check(header_name=name, mandatory=mandatory)
+
+def nameify(name):
+ return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
+
+def check_pkg(conf, name, **args):
+ if not 'mandatory' in args:
+ args['mandatory'] = True
+ "Check for a package iff it hasn't been checked for yet"
+ var_name = 'HAVE_' + nameify(args['uselib_store'])
+ check = not var_name in conf.env
+ if not check and 'atleast_version' in args:
+ # Re-check if version is newer than previous check
+ checked_version = conf.env['VERSION_' + name]
+ if checked_version and checked_version < args['atleast_version']:
+ check = True;
+ if check:
+ conf.check_cfg(package=name, args="--cflags --libs", **args)
+ found = bool(conf.env[var_name])
+ if found:
+ conf.define(var_name, int(found))
+ if 'atleast_version' in args:
+ conf.env['VERSION_' + name] = args['atleast_version']
+ else:
+ conf.undefine(var_name)
+ if args['mandatory'] == True:
+ conf.fatal("Required package " + name + " not found")
+
+def chop_prefix(conf, var):
+ name = conf.env[var][len(conf.env['PREFIX']):]
+ if len(name) > 0 and name[0] == '/':
+ name = name[1:]
+ if name == "":
+ name = "/"
+ return name;
+
+def configure(conf):
+ global g_step
+ if g_step > 1:
+ return
+ def append_cxx_flags(vals):
+ conf.env.append_value('CCFLAGS', vals.split())
+ conf.env.append_value('CXXFLAGS', vals.split())
+ conf.line_just = 43
+ conf.check_tool('misc')
+ conf.check_tool('compiler_cc')
+ conf.check_tool('compiler_cxx')
+ conf.env['BUILD_DOCS'] = Options.options.build_docs
+ conf.env['DEBUG'] = Options.options.debug
+ conf.env['STRICT'] = Options.options.strict
+ conf.env['PREFIX'] = os.path.abspath(os.path.expanduser(os.path.normpath(conf.env['PREFIX'])))
+ if Options.options.bundle:
+ conf.env['BUNDLE'] = True
+ conf.define('BUNDLE', 1)
+ conf.env['BINDIR'] = conf.env['PREFIX']
+ conf.env['INCLUDEDIR'] = os.path.join(conf.env['PREFIX'], 'Headers')
+ conf.env['LIBDIR'] = os.path.join(conf.env['PREFIX'], 'Libraries')
+ conf.env['DATADIR'] = os.path.join(conf.env['PREFIX'], 'Resources')
+ conf.env['HTMLDIR'] = os.path.join(conf.env['PREFIX'], 'Resources/Documentation')
+ conf.env['MANDIR'] = os.path.join(conf.env['PREFIX'], 'Resources/Man')
+ conf.env['LV2DIR'] = os.path.join(conf.env['PREFIX'], 'PlugIns')
+ else:
+ conf.env['BUNDLE'] = False
+ if Options.options.bindir:
+ conf.env['BINDIR'] = Options.options.bindir
+ else:
+ conf.env['BINDIR'] = os.path.join(conf.env['PREFIX'], 'bin')
+ if Options.options.includedir:
+ conf.env['INCLUDEDIR'] = Options.options.includedir
+ else:
+ conf.env['INCLUDEDIR'] = os.path.join(conf.env['PREFIX'], 'include')
+ if Options.options.libdir:
+ conf.env['LIBDIR'] = Options.options.libdir
+ else:
+ conf.env['LIBDIR'] = os.path.join(conf.env['PREFIX'], 'lib')
+ if Options.options.datadir:
+ conf.env['DATADIR'] = Options.options.datadir
+ else:
+ conf.env['DATADIR'] = os.path.join(conf.env['PREFIX'], 'share')
+ if Options.options.configdir:
+ conf.env['CONFIGDIR'] = Options.options.configdir
+ else:
+ conf.env['CONFIGDIR'] = os.path.join(conf.env['PREFIX'], 'etc')
+ if Options.options.htmldir:
+ conf.env['HTMLDIR'] = Options.options.htmldir
+ else:
+ conf.env['HTMLDIR'] = os.path.join(conf.env['DATADIR'], 'doc', Utils.g_module.APPNAME)
+ if Options.options.mandir:
+ conf.env['MANDIR'] = Options.options.mandir
+ else:
+ conf.env['MANDIR'] = os.path.join(conf.env['DATADIR'], 'man')
+ if Options.options.lv2dir:
+ conf.env['LV2DIR'] = Options.options.lv2dir
+ else:
+ if Options.options.lv2_user:
+ if sys.platform == "darwin":
+ conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
+ else:
+ conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
+ else:
+ if sys.platform == "darwin":
+ conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
+ else:
+ conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
+
+ conf.env['BINDIRNAME'] = chop_prefix(conf, 'BINDIR')
+ conf.env['LIBDIRNAME'] = chop_prefix(conf, 'LIBDIR')
+ conf.env['DATADIRNAME'] = chop_prefix(conf, 'DATADIR')
+ conf.env['CONFIGDIRNAME'] = chop_prefix(conf, 'CONFIGDIR')
+ conf.env['LV2DIRNAME'] = chop_prefix(conf, 'LV2DIR')
+
+ if Options.options.debug:
+ conf.env['CCFLAGS'] = [ '-O0', '-g' ]
+ conf.env['CXXFLAGS'] = [ '-O0', '-g' ]
+ else:
+ append_cxx_flags('-DNDEBUG')
+ if Options.options.strict:
+ conf.env.append_value('CCFLAGS', [ '-std=c99', '-pedantic' ])
+ conf.env.append_value('CXXFLAGS', [ '-ansi'])
+ append_cxx_flags('-Wall -Wextra -Wno-unused-parameter -Woverloaded-virtual')
+ append_cxx_flags('-fPIC -DPIC -fshow-column')
+ g_step = 2
+
+def set_local_lib(conf, name, has_objects):
+ conf.define('HAVE_' + nameify(name.upper()), 1)
+ if has_objects:
+ if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
+ conf.env['AUTOWAF_LOCAL_LIBS'] = {}
+ conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
+ else:
+ if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
+ conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
+ conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
+
+def use_lib(bld, obj, libs):
+ abssrcdir = os.path.abspath('.')
+ libs_list = libs.split()
+ for l in libs_list:
+ in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
+ in_libs = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
+ if in_libs:
+ if hasattr(obj, 'uselib_local'):
+ obj.uselib_local += ' lib' + l.lower() + ' '
+ else:
+ obj.uselib_local = 'lib' + l.lower() + ' '
+
+ if in_headers or in_libs:
+ inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
+ for f in ['CCFLAGS', 'CXXFLAGS']:
+ if not inc_flag in bld.env[f]:
+ bld.env.append_value(f, inc_flag)
+ else:
+ if hasattr(obj, 'uselib'):
+ obj.uselib += ' ' + l
+ else:
+ obj.uselib = l
+
+
+def display_header(title):
+ Utils.pprint('BOLD', title)
+
+def display_msg(conf, msg, status = None, color = None):
+ color = 'CYAN'
+ if type(status) == bool and status or status == "True":
+ color = 'GREEN'
+ elif type(status) == bool and not status or status == "False":
+ color = 'YELLOW'
+ Utils.pprint('NORMAL', "%s :" % msg.ljust(conf.line_just), sep='')
+ Utils.pprint(color, status)
+
+def print_summary(conf):
+ global g_step
+ if g_step > 2:
+ print
+ return
+ e = conf.env
+ print
+ display_header('Global configuration')
+ display_msg(conf, "Install prefix", conf.env['PREFIX'])
+ display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
+ display_msg(conf, "Strict compiler flags", str(conf.env['STRICT']))
+ display_msg(conf, "Build documentation", str(conf.env['BUILD_DOCS']))
+ print
+ g_step = 3
+
+def link_flags(env, lib):
+ return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
+
+def compile_flags(env, lib):
+ return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['CPPPATH_' + lib]))
+
+def set_recursive():
+ global g_is_child
+ g_is_child = True
+
+def is_child():
+ global g_is_child
+ return g_is_child
+
+# Pkg-config file
+def build_pc(bld, name, version, libs):
+ '''Build a pkg-config file for a library.
+ name -- uppercase variable name (e.g. 'SOMENAME')
+ version -- version string (e.g. '1.2.3')
+ libs -- string/list of dependencies (e.g. 'LIBFOO GLIB')
+ '''
+
+ obj = bld.new_task_gen('subst')
+ obj.source = name.lower() + '.pc.in'
+ obj.target = name.lower() + '.pc'
+ obj.install_path = '${PREFIX}/${LIBDIRNAME}/pkgconfig'
+ pkg_prefix = bld.env['PREFIX']
+ if pkg_prefix[-1] == '/':
+ pkg_prefix = pkg_prefix[:-1]
+ obj.dict = {
+ 'prefix' : pkg_prefix,
+ 'exec_prefix' : '${prefix}',
+ 'libdir' : '${exec_prefix}/lib',
+ 'includedir' : '${prefix}/include',
+ name + '_VERSION' : version,
+ }
+ if type(libs) != list:
+ libs = libs.split()
+ for i in libs:
+ obj.dict[i + '_LIBS'] = link_flags(bld.env, i)
+ obj.dict[i + '_CFLAGS'] = compile_flags(bld.env, i)
+
+# Doxygen API documentation
+def build_dox(bld, name, version, srcdir, blddir):
+ if not bld.env['BUILD_DOCS']:
+ return
+ obj = bld.new_task_gen('subst')
+ obj.source = 'doc/reference.doxygen.in'
+ obj.target = 'doc/reference.doxygen'
+ if is_child():
+ src_dir = os.path.join(srcdir, name.lower())
+ doc_dir = os.path.join(blddir, 'default', name.lower(), 'doc')
+ else:
+ src_dir = srcdir
+ doc_dir = os.path.join(blddir, 'default', 'doc')
+ obj.dict = {
+ name + '_VERSION' : version,
+ name + '_SRCDIR' : os.path.abspath(src_dir),
+ name + '_DOC_DIR' : os.path.abspath(doc_dir)
+ }
+ obj.install_path = ''
+ out1 = bld.new_task_gen('command-output')
+ out1.dependencies = [obj]
+ out1.stdout = '/doc/doxygen.out'
+ out1.stdin = '/doc/reference.doxygen' # whatever..
+ out1.command = 'doxygen'
+ out1.argv = [os.path.abspath(doc_dir) + '/reference.doxygen']
+ out1.command_is_external = True
+
+# Version code file generation
+def build_version_files(header_path, source_path, domain, major, minor, micro):
+ header_path = os.path.abspath(header_path)
+ source_path = os.path.abspath(source_path)
+ text = "int " + domain + "_major_version = " + str(major) + ";\n"
+ text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
+ text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
+ try:
+ o = file(source_path, 'w')
+ o.write(text)
+ o.close()
+ except IOError:
+ print "Could not open", source_path, " for writing\n"
+ sys.exit(-1)
+
+ text = "#ifndef __" + domain + "_version_h__\n"
+ text += "#define __" + domain + "_version_h__\n"
+ text += "extern const char* " + domain + "_revision;\n"
+ text += "extern int " + domain + "_major_version;\n"
+ text += "extern int " + domain + "_minor_version;\n"
+ text += "extern int " + domain + "_micro_version;\n"
+ text += "#endif /* __" + domain + "_version_h__ */\n"
+ try:
+ o = file(header_path, 'w')
+ o.write(text)
+ o.close()
+ except IOError:
+ print "Could not open", header_path, " for writing\n"
+ sys.exit(-1)
+
+ return None
+
+def shutdown():
+ # This isn't really correct (for packaging), but people asking is annoying
+ if Options.commands['install']:
+ try: os.popen("/sbin/ldconfig")
+ except: pass
+
diff --git a/libs/audiographer/src/gdither/gdither.cc b/libs/audiographer/src/gdither/gdither.cc
new file mode 100644
index 0000000000..966da47b06
--- /dev/null
+++ b/libs/audiographer/src/gdither/gdither.cc
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include "gdither_types_internal.h"
+#include "gdither.h"
+#include "noise.h"
+
+/* this monstrosity is necessary to get access to lrintf() and random().
+ whoever is writing the glibc headers <cmath> and <cstdlib> should be
+ hauled off to a programmer re-education camp. for the rest of
+ their natural lives. or longer. <paul@linuxaudiosystems.com>
+*/
+
+#define _ISOC9X_SOURCE 1
+#define _ISOC99_SOURCE 1
+#ifdef __cplusplus
+#include <cmath>
+#else
+#include <math.h>
+#endif
+
+#undef __USE_SVID
+#define __USE_SVID 1
+#ifdef __cplusplus
+#include <cstdlib>
+#else
+#include <stdlib.h>
+#endif
+
+#include <sys/types.h>
+
+/* Lipshitz's minimally audible FIR, only really works for 46kHz-ish signals */
+static const float shaped_bs[] = { 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f };
+
+/* Some useful constants */
+#define MAX_U8 255
+#define MIN_U8 0
+#define SCALE_U8 128.0f
+
+#define MAX_S16 32767
+#define MIN_S16 -32768
+#define SCALE_S16 32768.0f
+
+#define MAX_S24 8388607
+#define MIN_S24 -8388608
+#define SCALE_S24 8388608.0f
+
+GDither gdither_new(GDitherType type, uint32_t channels,
+
+ GDitherSize bit_depth, int dither_depth)
+{
+ GDither s;
+
+ s = (GDither)calloc(1, sizeof(struct GDither_s));
+ s->type = type;
+ s->channels = channels;
+ s->bit_depth = (int)bit_depth;
+
+ if (dither_depth <= 0 || dither_depth > (int)bit_depth) {
+ dither_depth = (int)bit_depth;
+ }
+ s->dither_depth = dither_depth;
+
+ s->scale = (float)(1LL << (dither_depth - 1));
+ if (bit_depth == GDitherFloat || bit_depth == GDitherDouble) {
+ s->post_scale_fp = 1.0f / s->scale;
+ s->post_scale = 0;
+ } else {
+ s->post_scale_fp = 0.0f;
+ s->post_scale = 1 << ((int)bit_depth - dither_depth);
+ }
+
+ switch (bit_depth) {
+ case GDither8bit:
+ /* Unsigned 8 bit */
+ s->bias = 1.0f;
+ s->clamp_u = 255;
+ s->clamp_l = 0;
+ break;
+ case GDither16bit:
+ /* Signed 16 bit */
+ s->bias = 0.0f;
+ s->clamp_u = 32767;
+ s->clamp_l = -32768;
+ break;
+ case GDither32bit:
+ /* Signed 24 bit, in upper 24 bits of 32 bit word */
+ s->bias = 0.0f;
+ s->clamp_u = 8388607;
+ s->clamp_l = -8388608;
+ break;
+ case GDitherFloat:
+ /* normalised float */
+ s->bias = 0.0f;
+ s->clamp_u = lrintf(s->scale);
+ s->clamp_l = lrintf(-s->scale);
+ break;
+ case GDitherDouble:
+ /* normalised float */
+ s->bias = 0.0f;
+ s->clamp_u = lrintf(s->scale);
+ s->clamp_l = lrintf(-s->scale);
+ break;
+ case 23:
+ /* special performance test case */
+ s->scale = SCALE_S24;
+ s->post_scale = 256;
+ s->bias = 0.0f;
+ s->clamp_u = 8388607;
+ s->clamp_l = -8388608;
+ break;
+ default:
+ /* Not a bit depth we can handle */
+ free(s);
+
+ return NULL;
+ break;
+ }
+
+ switch (type) {
+ case GDitherNone:
+ case GDitherRect:
+ /* No state */
+ break;
+
+ case GDitherTri:
+ /* The last whitenoise sample */
+ s->tri_state = (float *) calloc(channels, sizeof(float));
+ break;
+
+ case GDitherShaped:
+ /* The error from the last few samples encoded */
+ s->shaped_state = (GDitherShapedState*)
+ calloc(channels, sizeof(GDitherShapedState));
+ break;
+ }
+
+ return s;
+}
+
+void gdither_free(GDither s)
+{
+ if (s) {
+ free(s->tri_state);
+ free(s->shaped_state);
+ free(s);
+ }
+}
+
+inline static void gdither_innner_loop(const GDitherType dt,
+ const uint32_t stride, const float bias, const float scale,
+
+ const uint32_t post_scale, const int bit_depth,
+ const uint32_t channel, const uint32_t length, float *ts,
+
+ GDitherShapedState *ss, float const *x, void *y, const int clamp_u,
+
+ const int clamp_l)
+{
+ uint32_t pos, i;
+ uint8_t *o8 = (uint8_t*) y;
+ int16_t *o16 = (int16_t*) y;
+ int32_t *o32 = (int32_t*) y;
+ float tmp, r, ideal;
+ int64_t clamped;
+
+ i = channel;
+ for (pos = 0; pos < length; pos++, i += stride) {
+ tmp = x[i] * scale + bias;
+
+ switch (dt) {
+ case GDitherNone:
+ break;
+ case GDitherRect:
+ tmp -= GDITHER_NOISE;
+ break;
+ case GDitherTri:
+ r = GDITHER_NOISE - 0.5f;
+ tmp -= r - ts[channel];
+ ts[channel] = r;
+ break;
+ case GDitherShaped:
+ /* Save raw value for error calculations */
+ ideal = tmp;
+
+ /* Run FIR and add white noise */
+ ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f;
+ tmp += ss->buffer[ss->phase] * shaped_bs[0]
+ + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[1]
+ + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[2]
+ + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[3]
+ + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[4];
+
+ /* Roll buffer and store last error */
+ ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK;
+ ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal;
+ break;
+ }
+
+ clamped = lrintf(tmp);
+ if (clamped > clamp_u) {
+ clamped = clamp_u;
+ } else if (clamped < clamp_l) {
+ clamped = clamp_l;
+ }
+
+ switch (bit_depth) {
+ case GDither8bit:
+ o8[i] = (u_int8_t) (clamped * post_scale);
+ break;
+ case GDither16bit:
+ o16[i] = (int16_t) (clamped * post_scale);
+ break;
+ case GDither32bit:
+ o32[i] = (int32_t) (clamped * post_scale);
+ break;
+ }
+ }
+}
+
+/* floating pint version of the inner loop function */
+inline static void gdither_innner_loop_fp(const GDitherType dt,
+ const uint32_t stride, const float bias, const float scale,
+
+ const float post_scale, const int bit_depth,
+ const uint32_t channel, const uint32_t length, float *ts,
+
+ GDitherShapedState *ss, float const *x, void *y, const int clamp_u,
+
+ const int clamp_l)
+{
+ uint32_t pos, i;
+ float *oflt = (float*) y;
+ double *odbl = (double*) y;
+ float tmp, r, ideal;
+ double clamped;
+
+ i = channel;
+ for (pos = 0; pos < length; pos++, i += stride) {
+ tmp = x[i] * scale + bias;
+
+ switch (dt) {
+ case GDitherNone:
+ break;
+ case GDitherRect:
+ tmp -= GDITHER_NOISE;
+ break;
+ case GDitherTri:
+ r = GDITHER_NOISE - 0.5f;
+ tmp -= r - ts[channel];
+ ts[channel] = r;
+ break;
+ case GDitherShaped:
+ /* Save raw value for error calculations */
+ ideal = tmp;
+
+ /* Run FIR and add white noise */
+ ss->buffer[ss->phase] = GDITHER_NOISE * 0.5f;
+ tmp += ss->buffer[ss->phase] * shaped_bs[0]
+ + ss->buffer[(ss->phase - 1) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[1]
+ + ss->buffer[(ss->phase - 2) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[2]
+ + ss->buffer[(ss->phase - 3) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[3]
+ + ss->buffer[(ss->phase - 4) & GDITHER_SH_BUF_MASK]
+ * shaped_bs[4];
+
+ /* Roll buffer and store last error */
+ ss->phase = (ss->phase + 1) & GDITHER_SH_BUF_MASK;
+ ss->buffer[ss->phase] = (float)lrintf(tmp) - ideal;
+ break;
+ }
+
+ clamped = rintf(tmp);
+ if (clamped > clamp_u) {
+ clamped = clamp_u;
+ } else if (clamped < clamp_l) {
+ clamped = clamp_l;
+ }
+
+ switch (bit_depth) {
+ case GDitherFloat:
+ oflt[i] = (float) (clamped * post_scale);
+ break;
+ case GDitherDouble:
+ odbl[i] = (double) (clamped * post_scale);
+ break;
+ }
+ }
+}
+
+#define GDITHER_CONV_BLOCK 512
+
+void gdither_run(GDither s, uint32_t channel, uint32_t length,
+ double const *x, void *y)
+{
+ float conv[GDITHER_CONV_BLOCK];
+ uint32_t i, pos;
+ char *ycast = (char *)y;
+
+ int step;
+
+ switch (s->bit_depth) {
+ case GDither8bit:
+ step = 1;
+ break;
+ case GDither16bit:
+ step = 2;
+ break;
+ case GDither32bit:
+ case GDitherFloat:
+ step = 4;
+ break;
+ case GDitherDouble:
+ step = 8;
+ break;
+ default:
+ step = 0;
+ break;
+ }
+
+ pos = 0;
+ while (pos < length) {
+ for (i=0; (i + pos) < length && i < GDITHER_CONV_BLOCK; i++) {
+ conv[i] = x[pos + i];
+ }
+ gdither_runf(s, channel, i, conv, ycast + s->channels * step);
+ pos += i;
+ }
+}
+
+void gdither_runf(GDither s, uint32_t channel, uint32_t length,
+ float const *x, void *y)
+{
+ uint32_t pos, i;
+ float tmp;
+ int64_t clamped;
+ GDitherShapedState *ss = NULL;
+
+ if (!s || channel >= s->channels) {
+ return;
+ }
+
+ if (s->shaped_state) {
+ ss = s->shaped_state + channel;
+ }
+
+ if (s->type == GDitherNone && s->bit_depth == 23) {
+ int32_t *o32 = (int32_t*) y;
+
+ for (pos = 0; pos < length; pos++) {
+ i = channel + (pos * s->channels);
+ tmp = x[i] * 8388608.0f;
+
+ clamped = lrintf(tmp);
+ if (clamped > 8388607) {
+ clamped = 8388607;
+ } else if (clamped < -8388608) {
+ clamped = -8388608;
+ }
+
+ o32[i] = (int32_t) (clamped * 256);
+ }
+
+ return;
+ }
+
+ /* some common case handling code - looks a bit wierd, but it allows
+ * the compiler to optimise out the branches in the inner loop */
+ if (s->bit_depth == 8 && s->dither_depth == 8) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL, NULL, x, y,
+ MAX_U8, MIN_U8);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL, NULL, x, y,
+ MAX_U8, MIN_U8);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, s->tri_state,
+ NULL, x, y, MAX_U8, MIN_U8);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 128.0f, SCALE_U8,
+ 1, 8, channel, length, NULL,
+ ss, x, y, MAX_U8, MIN_U8);
+ break;
+ }
+ } else if (s->bit_depth == 16 && s->dither_depth == 16) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, NULL, NULL, x, y,
+ MAX_S16, MIN_S16);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, NULL, NULL, x, y,
+ MAX_S16, MIN_S16);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S16,
+ 1, 16, channel, length, s->tri_state,
+ NULL, x, y, MAX_S16, MIN_S16);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 0.0f,
+ SCALE_S16, 1, 16, channel, length, NULL,
+ ss, x, y, MAX_S16, MIN_S16);
+ break;
+ }
+ } else if (s->bit_depth == 32 && s->dither_depth == 24) {
+ switch (s->type) {
+ case GDitherNone:
+ gdither_innner_loop(GDitherNone, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, NULL, NULL, x,
+ y, MAX_S24, MIN_S24);
+ break;
+ case GDitherRect:
+ gdither_innner_loop(GDitherRect, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, NULL, NULL, x,
+ y, MAX_S24, MIN_S24);
+ break;
+ case GDitherTri:
+ gdither_innner_loop(GDitherTri, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length, s->tri_state,
+ NULL, x, y, MAX_S24, MIN_S24);
+ break;
+ case GDitherShaped:
+ gdither_innner_loop(GDitherShaped, s->channels, 0.0f, SCALE_S24,
+ 256, 32, channel, length,
+ NULL, ss, x, y, MAX_S24, MIN_S24);
+ break;
+ }
+ } else if (s->bit_depth == GDitherFloat || s->bit_depth == GDitherDouble) {
+ gdither_innner_loop_fp(s->type, s->channels, s->bias, s->scale,
+ s->post_scale_fp, s->bit_depth, channel, length,
+ s->tri_state, ss, x, y, s->clamp_u, s->clamp_l);
+ } else {
+ /* no special case handling, just process it from the struct */
+
+ gdither_innner_loop(s->type, s->channels, s->bias, s->scale,
+ s->post_scale, s->bit_depth, channel,
+ length, s->tri_state, ss, x, y, s->clamp_u,
+ s->clamp_l);
+ }
+}
+
+/* vi:set ts=8 sts=4 sw=4: */
diff --git a/libs/audiographer/src/gdither/gdither.h b/libs/audiographer/src/gdither/gdither.h
new file mode 100644
index 0000000000..d2b2657f32
--- /dev/null
+++ b/libs/audiographer/src/gdither/gdither.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef GDITHER_H
+#define GDITHER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gdither_types.h"
+
+/* Create and initialise a state structure, takes a dither type, a number of
+ * channels and a bit depth as input
+ *
+ * The Dither type is one of
+ *
+ * GDitherNone - straight nearest neighbour rounding. Theres no pressing
+ * reason to do this at 8 or 16 bit, but you might want to at 24, for some
+ * reason. At the lest it will save you writing int->float conversion code,
+ * which is arder than it sounds.
+ *
+ * GDitherRect - mathematically most accurate, lowest noise floor, but not
+ * that good for audio. It is the fastest though.
+ *
+ * GDitherTri - a happy medium between Rectangular and Shaped, reasonable
+ * noise floor, not too obvious, quite fast.
+ *
+ * GDitherShaped - should have the least audible impact, but has the highest
+ * noise floor, fairly CPU intensive. Not advisible if your going to apply
+ * any frequency manipulation afterwards.
+ *
+ * channels, sets the number of channels in the output data, output data will
+ * be written interleaved into the area given to gdither_run(). Set to 1
+ * if you are not working with interleaved buffers.
+ *
+ * bit depth, sets the bit width of the output sample data, it can be one of:
+ *
+ * GDither8bit - 8 bit unsiged
+ * GDither16bit - 16 bit signed
+ * GDither32bit - 24+bits in upper bits of a 32 bit word
+ * GDitherFloat - IEEE floating point (32bits)
+ * GDitherDouble - Double precision IEEE floating point (64bits)
+ *
+ * dither_depth, set the number of bits before the signal will be truncated to,
+ * eg. 16 will produce an output stream with 16bits-worth of signal. Setting to
+ * zero or greater than the width of the output format will dither to the
+ * maximum precision allowed by the output format.
+ */
+GDither gdither_new(GDitherType type, uint32_t channels,
+
+ GDitherSize bit_depth, int dither_depth);
+
+/* Frees memory used by gdither_new.
+ */
+void gdither_free(GDither s);
+
+/* Applies dithering to the supplied signal.
+ *
+ * channel is the channel number you are processing (0 - channles-1), length is
+ * the length of the input, in samples, x is the input samples (float), y is
+ * where the output samples will be written, it should have the approaprate
+ * type for the chosen bit depth
+ */
+void gdither_runf(GDither s, uint32_t channel, uint32_t length,
+ float const *x, void *y);
+
+/* see gdither_runf, vut input argument is double format */
+void gdither_run(GDither s, uint32_t channel, uint32_t length,
+ double const *x, void *y);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/audiographer/src/gdither/gdither_types.h b/libs/audiographer/src/gdither/gdither_types.h
new file mode 100644
index 0000000000..bcc0097d7f
--- /dev/null
+++ b/libs/audiographer/src/gdither/gdither_types.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef GDITHER_TYPES_H
+#define GDITHER_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ GDitherNone = 0,
+ GDitherRect,
+ GDitherTri,
+ GDitherShaped
+} GDitherType;
+
+typedef enum {
+ GDither8bit = 8,
+ GDither16bit = 16,
+ GDither32bit = 32,
+ GDitherFloat = 25,
+ GDitherDouble = 54
+} GDitherSize;
+
+typedef void *GDither;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/audiographer/src/gdither/gdither_types_internal.h b/libs/audiographer/src/gdither/gdither_types_internal.h
new file mode 100644
index 0000000000..6cb0c48af9
--- /dev/null
+++ b/libs/audiographer/src/gdither/gdither_types_internal.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2002 Steve Harris <steve@plugin.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef GDITHER_TYPES_H
+#define GDITHER_TYPES_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GDITHER_SH_BUF_SIZE 8
+#define GDITHER_SH_BUF_MASK 7
+
+/* this must agree with whats in gdither_types.h */
+typedef enum {
+ GDitherNone = 0,
+ GDitherRect,
+ GDitherTri,
+ GDitherShaped
+} GDitherType;
+
+typedef enum {
+ GDither8bit = 8,
+ GDither16bit = 16,
+ GDither32bit = 32,
+ GDitherFloat = 25,
+ GDitherDouble = 54
+} GDitherSize;
+
+typedef struct {
+ uint32_t phase;
+ float buffer[GDITHER_SH_BUF_SIZE];
+} GDitherShapedState;
+
+typedef struct GDither_s {
+ GDitherType type;
+ uint32_t channels;
+ uint32_t bit_depth;
+ uint32_t dither_depth;
+ float scale;
+ uint32_t post_scale;
+ float post_scale_fp;
+ float bias;
+
+ int clamp_u;
+
+ int clamp_l;
+ float *tri_state;
+ GDitherShapedState *shaped_state;
+} *GDither;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libs/audiographer/src/gdither/noise.h b/libs/audiographer/src/gdither/noise.h
new file mode 100644
index 0000000000..96a582ef9b
--- /dev/null
+++ b/libs/audiographer/src/gdither/noise.h
@@ -0,0 +1,38 @@
+/*
+ Copyright (C) 2000-2007 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef NOISE_H
+#define NOISE_H
+
+/* Can be overrriden with any code that produces whitenoise between 0.0f and
+ * 1.0f, eg (random() / (float)RAND_MAX) should be a good source of noise, but
+ * its expensive */
+#ifndef GDITHER_NOISE
+#define GDITHER_NOISE gdither_noise()
+#endif
+
+inline static float gdither_noise()
+{
+ static uint32_t rnd = 23232323;
+ rnd = (rnd * 196314165) + 907633515;
+
+ return rnd * 2.3283064365387e-10f;
+}
+
+#endif
diff --git a/libs/audiographer/src/routines.cc b/libs/audiographer/src/routines.cc
new file mode 100644
index 0000000000..b97653be75
--- /dev/null
+++ b/libs/audiographer/src/routines.cc
@@ -0,0 +1,7 @@
+#include "audiographer/routines.h"
+
+namespace AudioGrapher
+{
+Routines::compute_peak_t Routines::_compute_peak = &Routines::default_compute_peak;
+Routines::apply_gain_to_buffer_t Routines::_apply_gain_to_buffer = &Routines::default_apply_gain_to_buffer;
+}
diff --git a/libs/audiographer/src/sample_format_converter.cc b/libs/audiographer/src/sample_format_converter.cc
new file mode 100644
index 0000000000..5b2d3d6e8c
--- /dev/null
+++ b/libs/audiographer/src/sample_format_converter.cc
@@ -0,0 +1,190 @@
+#include "audiographer/sample_format_converter.h"
+
+#include "gdither/gdither.h"
+#include "audiographer/exception.h"
+
+#include <boost/format.hpp>
+
+#include <cstring>
+
+namespace AudioGrapher
+{
+
+template <typename TOut>
+SampleFormatConverter<TOut>::SampleFormatConverter (uint32_t channels) :
+ channels (channels),
+ dither (0),
+ data_out_size (0),
+ data_out (0),
+ clip_floats (false)
+{
+}
+
+template <>
+void
+SampleFormatConverter<float>::init (nframes_t max_frames, int type, int data_width)
+{
+ if (data_width != 32) { throw Exception (*this, "Unsupported data width"); }
+ init_common (max_frames);
+ dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width);
+}
+
+template <>
+void
+SampleFormatConverter<int32_t>::init (nframes_t max_frames, int type, int data_width)
+{
+ if(data_width < 24) { throw Exception (*this, "Use SampleFormatConverter<int16_t> for data widths < 24"); }
+
+ init_common (max_frames);
+
+ if (data_width == 24) {
+ dither = gdither_new ((GDitherType) type, channels, GDither32bit, data_width);
+ } else if (data_width == 32) {
+ dither = gdither_new (GDitherNone, channels, GDitherFloat, data_width);
+ } else {
+ throw Exception (*this, "Unsupported data width");
+ }
+}
+
+template <>
+void
+SampleFormatConverter<int16_t>::init (nframes_t max_frames, int type, int data_width)
+{
+ if (data_width != 16) { throw Exception (*this, "Unsupported data width"); }
+ init_common (max_frames);
+ dither = gdither_new ((GDitherType) type, channels, GDither16bit, data_width);
+}
+
+template <>
+void
+SampleFormatConverter<uint8_t>::init (nframes_t max_frames, int type, int data_width)
+{
+ if (data_width != 8) { throw Exception (*this, "Unsupported data width"); }
+ init_common (max_frames);
+ dither = gdither_new ((GDitherType) type, channels, GDither8bit, data_width);
+}
+
+template <typename TOut>
+void
+SampleFormatConverter<TOut>::init_common (nframes_t max_frames )
+{
+ reset();
+ if (max_frames > data_out_size) {
+
+ delete[] data_out;
+
+ data_out = new TOut[max_frames];
+ data_out_size = max_frames;
+ }
+}
+
+template <typename TOut>
+SampleFormatConverter<TOut>::~SampleFormatConverter ()
+{
+ reset();
+}
+
+template <typename TOut>
+void
+SampleFormatConverter<TOut>::reset()
+{
+ if (dither) {
+ gdither_free (dither);
+ dither = 0;
+ }
+
+ delete[] data_out;
+ data_out_size = 0;
+ data_out = 0;
+
+ clip_floats = false;
+}
+
+/* Basic const version of process() */
+template <typename TOut>
+void
+SampleFormatConverter<TOut>::process (ProcessContext<float> const & c_in)
+{
+ float const * const data = c_in.data();
+ nframes_t const frames = c_in.frames();
+
+ check_frame_count (frames);
+
+ /* Do conversion */
+
+ for (uint32_t chn = 0; chn < channels; ++chn) {
+ gdither_runf (dither, chn, frames / channels, data, data_out);
+ }
+
+ /* Write forward */
+
+ ProcessContext<TOut> c_out(c_in, data_out);
+ output (c_out);
+}
+
+/* Basic non-const version of process(), calls the const one */
+template<typename TOut>
+void
+SampleFormatConverter<TOut>::process (ProcessContext<float> & c_in)
+{
+ process (static_cast<ProcessContext<float> const &> (c_in));
+}
+
+/* template specialization for float, in-place processing (non-const) */
+template<>
+void
+SampleFormatConverter<float>::process (ProcessContext<float> & c_in)
+{
+ nframes_t frames = c_in.frames();
+ float * data = c_in.data();
+
+ if (clip_floats) {
+ for (nframes_t x = 0; x < frames; ++x) {
+ if (data[x] > 1.0f) {
+ data[x] = 1.0f;
+ } else if (data[x] < -1.0f) {
+ data[x] = -1.0f;
+ }
+ }
+ }
+
+ output (c_in);
+}
+
+/* template specialized const version, copies the data, and calls the non-const version */
+template<>
+void
+SampleFormatConverter<float>::process (ProcessContext<float> const & c_in)
+{
+ // Make copy of data and pass it to non-const version
+ nframes_t frames = c_in.frames();
+ check_frame_count (frames);
+ memcpy (data_out, c_in.data(), frames * sizeof(float));
+
+ ProcessContext<float> c (c_in, data_out);
+ process (c);
+}
+
+template<typename TOut>
+void
+SampleFormatConverter<TOut>::check_frame_count(nframes_t frames)
+{
+ if (frames % channels != 0) {
+ throw Exception (*this, boost::str (boost::format (
+ "Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels")
+ % frames % channels));
+ }
+
+ if (frames > data_out_size) {
+ throw Exception (*this, boost::str (boost::format (
+ "Too many frames given to process(), %1% instad of %2%")
+ % frames % data_out_size));
+ }
+}
+
+template class SampleFormatConverter<uint8_t>;
+template class SampleFormatConverter<int16_t>;
+template class SampleFormatConverter<int32_t>;
+template class SampleFormatConverter<float>;
+
+} // namespace
diff --git a/libs/audiographer/src/sndfile_base.cc b/libs/audiographer/src/sndfile_base.cc
new file mode 100644
index 0000000000..8d12f9341b
--- /dev/null
+++ b/libs/audiographer/src/sndfile_base.cc
@@ -0,0 +1,56 @@
+#include "audiographer/sndfile_base.h"
+#include "audiographer/exception.h"
+
+#include <boost/format.hpp>
+
+namespace AudioGrapher
+{
+
+using std::string;
+using boost::str;
+using boost::format;
+
+/* SndfileWriterBase */
+
+SndfileBase::SndfileBase (ChannelCount channels, nframes_t samplerate, int format, string const & path)
+ : path (path)
+{
+ char errbuf[256];
+
+ sf_info.channels = channels;
+ sf_info.samplerate = samplerate;
+ sf_info.format = format;
+
+ if (!sf_format_check (&sf_info)) {
+ throw Exception (*this, "Invalid format in constructor");
+ }
+
+ if (path.length() == 0) {
+ throw Exception (*this, "No output file specified");
+ }
+
+ /* TODO add checks that the directory path exists, and also
+ check if we are overwriting an existing file...
+ */
+
+ // Open file
+ if (path.compare ("temp")) {
+ if ((sndfile = sf_open (path.c_str(), SFM_WRITE, &sf_info)) == 0) {
+ sf_error_str (0, errbuf, sizeof (errbuf) - 1);
+ throw Exception (*this, str (boost::format ("Cannot open output file \"%1%\" (%2%)") % path % errbuf));
+ }
+ } else {
+ FILE * file;
+ if (!(file = tmpfile ())) {
+ throw Exception (*this, "Cannot open tempfile");
+ }
+ sndfile = sf_open_fd (fileno(file), SFM_RDWR, &sf_info, true);
+ }
+}
+
+SndfileBase::~SndfileBase ()
+{
+ sf_close (sndfile);
+}
+
+} // namespace
diff --git a/libs/audiographer/src/sndfile_reader.cc b/libs/audiographer/src/sndfile_reader.cc
new file mode 100644
index 0000000000..0508110314
--- /dev/null
+++ b/libs/audiographer/src/sndfile_reader.cc
@@ -0,0 +1,67 @@
+#include "audiographer/sndfile_reader.h"
+
+#include <boost/format.hpp>
+
+#include "audiographer/exception.h"
+
+namespace AudioGrapher
+{
+
+template<typename T>
+SndfileReader<T>::SndfileReader (ChannelCount channels, nframes_t samplerate, int format, std::string path)
+ : SndfileBase (channels, samplerate, format, path)
+{
+ init ();
+}
+
+template<typename T>
+nframes_t
+SndfileReader<T>::seek (nframes_t frames, SeekType whence)
+{
+ return sf_seek (sndfile, frames, whence);
+}
+
+template<typename T>
+nframes_t
+SndfileReader<T>::read (ProcessContext<T> & context)
+{
+ if (context.channels() != sf_info.channels) {
+ throw Exception (*this, boost::str (boost::format (
+ "ProcessContext given to read() has a wrong amount of channels: %1% instead of %2%")
+ % context.channels() % sf_info.channels));
+ }
+
+ nframes_t frames_read = (*read_func) (sndfile, context.data(), context.frames());
+ if (frames_read < context.frames()) {
+ context.set_flag (ProcessContext<T>::EndOfInput);
+ }
+ output (context);
+ return frames_read;
+}
+
+template<>
+void
+SndfileReader<short>::init()
+{
+ read_func = &sf_read_short;
+}
+
+template<>
+void
+SndfileReader<int>::init()
+{
+ read_func = &sf_read_int;
+}
+
+template<>
+void
+SndfileReader<float>::init()
+{
+ read_func = &sf_read_float;
+}
+
+template class SndfileReader<short>;
+template class SndfileReader<int>;
+template class SndfileReader<float>;
+
+} // namespace \ No newline at end of file
diff --git a/libs/audiographer/src/sndfile_writer.cc b/libs/audiographer/src/sndfile_writer.cc
new file mode 100644
index 0000000000..d12d6b943b
--- /dev/null
+++ b/libs/audiographer/src/sndfile_writer.cc
@@ -0,0 +1,73 @@
+#include "audiographer/sndfile_writer.h"
+#include "audiographer/exception.h"
+
+#include <cstring>
+
+#include <boost/format.hpp>
+
+namespace AudioGrapher
+{
+
+using std::string;
+using boost::str;
+using boost::format;
+
+template <typename T>
+SndfileWriter<T>::SndfileWriter (ChannelCount channels, nframes_t samplerate, int format, string const & path) :
+ SndfileBase (channels, samplerate, format, path)
+{
+ // init write function
+ init ();
+}
+
+template <>
+void
+SndfileWriter<float>::init ()
+{
+ write_func = &sf_write_float;
+}
+
+template <>
+void
+SndfileWriter<int>::init ()
+{
+ write_func = &sf_write_int;
+}
+
+template <>
+void
+SndfileWriter<short>::init ()
+{
+ write_func = &sf_write_short;
+}
+
+template <typename T>
+void
+SndfileWriter<T>::process (ProcessContext<T> const & c)
+{
+ if (c.channels() != sf_info.channels) {
+ throw Exception (*this, str (boost::format(
+ "Wrong number of channels given to process(), %1% instead of %2%")
+ % c.channels() % sf_info.channels));
+ }
+
+ char errbuf[256];
+ nframes_t written = (*write_func) (sndfile, c.data(), c.frames());
+ if (written != c.frames()) {
+ sf_error_str (sndfile, errbuf, sizeof (errbuf) - 1);
+ throw Exception (*this, str ( format("Could not write data to output file (%1%)") % errbuf));
+ }
+
+ if (c.has_flag(ProcessContext<T>::EndOfInput)) {
+ sf_write_sync (sndfile);
+ //#ifdef HAVE_SIGCPP
+ FileWritten (path);
+ //#endif
+ }
+}
+
+template class SndfileWriter<short>;
+template class SndfileWriter<int>;
+template class SndfileWriter<float>;
+
+} // namespace
diff --git a/libs/audiographer/src/sr_converter.cc b/libs/audiographer/src/sr_converter.cc
new file mode 100644
index 0000000000..c61b3d0728
--- /dev/null
+++ b/libs/audiographer/src/sr_converter.cc
@@ -0,0 +1,218 @@
+#include "audiographer/sr_converter.h"
+#include "audiographer/exception.h"
+
+#include <cmath>
+#include <cstring>
+#include <boost/format.hpp>
+
+#define ENABLE_DEBUG 0
+
+#if ENABLE_DEBUG
+ #include <iostream>
+ #define DEBUG(str) std::cout << str << std::endl;
+#else
+ #define DEBUG(str)
+#endif
+
+namespace AudioGrapher
+{
+using boost::format;
+using boost::str;
+
+SampleRateConverter::SampleRateConverter (uint32_t channels)
+ : active (false)
+ , channels (channels)
+ , max_frames_in(0)
+ , leftover_data (0)
+ , leftover_frames (0)
+ , max_leftover_frames (0)
+ , data_out (0)
+ , data_out_size (0)
+ , src_state (0)
+{
+}
+
+void
+SampleRateConverter::init (nframes_t in_rate, nframes_t out_rate, int quality)
+{
+ reset();
+
+ if (in_rate == out_rate) {
+ src_data.src_ratio = 1;
+ return;
+ }
+
+ active = true;
+ int err;
+ if ((src_state = src_new (quality, channels, &err)) == 0) {
+ throw Exception (*this, str (format ("Cannot initialize sample rate converter: %1%") % src_strerror (err)));
+ }
+
+ src_data.src_ratio = (double) out_rate / (double) in_rate;
+}
+
+SampleRateConverter::~SampleRateConverter ()
+{
+ reset();
+}
+
+nframes_t
+SampleRateConverter::allocate_buffers (nframes_t max_frames)
+{
+ if (!active) { return max_frames; }
+
+ nframes_t max_frames_out = (nframes_t) ceil (max_frames * src_data.src_ratio);
+ if (data_out_size < max_frames_out) {
+
+ delete[] data_out;
+ data_out = new float[max_frames_out];
+ src_data.data_out = data_out;
+
+ max_leftover_frames = 4 * max_frames;
+ leftover_data = (float *) realloc (leftover_data, max_leftover_frames * sizeof (float));
+ if (!leftover_data) {
+ throw Exception (*this, "A memory allocation error occured");
+ }
+
+ max_frames_in = max_frames;
+ data_out_size = max_frames_out;
+ }
+
+ return max_frames_out;
+}
+
+void
+SampleRateConverter::process (ProcessContext<float> const & c)
+{
+ if (!active) {
+ output (c);
+ return;
+ }
+
+ nframes_t frames = c.frames();
+ float * in = const_cast<float *> (c.data()); // TODO check if this is safe!
+
+ if (frames > max_frames_in) {
+ throw Exception (*this, str (format (
+ "process() called with too many frames, %1% instead of %2%")
+ % frames % max_frames_in));
+ }
+
+ if (frames % channels != 0) {
+ throw Exception (*this, boost::str (boost::format (
+ "Number of frames given to process() was not a multiple of channels: %1% frames with %2% channels")
+ % frames % channels));
+ }
+
+ int err;
+ bool first_time = true;
+
+ do {
+ src_data.output_frames = data_out_size / channels;
+ src_data.data_out = data_out;
+
+ if (leftover_frames > 0) {
+
+ /* input data will be in leftover_data rather than data_in */
+
+ src_data.data_in = leftover_data;
+
+ if (first_time) {
+
+ /* first time, append new data from data_in into the leftover_data buffer */
+
+ memcpy (&leftover_data [leftover_frames * channels], in, frames * sizeof(float));
+ src_data.input_frames = frames + leftover_frames;
+ } else {
+
+ /* otherwise, just use whatever is still left in leftover_data; the contents
+ were adjusted using memmove() right after the last SRC call (see
+ below)
+ */
+
+ src_data.input_frames = leftover_frames;
+ }
+
+ } else {
+ src_data.data_in = in;
+ src_data.input_frames = frames / channels;
+ }
+
+ first_time = false;
+
+ DEBUG ("data_in: " << src_data.data_in);
+ DEBUG ("input_frames: " << src_data.input_frames);
+ DEBUG ("data_out: " << src_data.data_out);
+ DEBUG ("output_frames: " << src_data.output_frames);
+
+ if ((err = src_process (src_state, &src_data)) != 0) {
+ throw Exception (*this, str (format ("An error occured during sample rate conversion: %1%") % src_strerror (err)));
+ }
+
+ leftover_frames = src_data.input_frames - src_data.input_frames_used;
+
+ if (leftover_frames > 0) {
+ if (leftover_frames > max_leftover_frames) {
+ throw Exception(*this, "leftover frames overflowed");
+ }
+ memmove (leftover_data, (char *) &src_data.data_in[src_data.input_frames_used * channels],
+ leftover_frames * channels * sizeof(float));
+ }
+
+ ProcessContext<float> c_out (c, data_out, src_data.output_frames_gen * channels);
+ if (!src_data.end_of_input || leftover_frames) {
+ c_out.remove_flag (ProcessContext<float>::EndOfInput);
+ }
+ output (c_out);
+
+ DEBUG ("src_data.output_frames_gen: " << src_data.output_frames_gen << ", leftover_frames: " << leftover_frames);
+
+ if (src_data.output_frames_gen == 0 && leftover_frames) { throw Exception (*this, boost::str (boost::format (
+ "No output frames genereated with %1% leftover frames")
+ % leftover_frames)); }
+
+ } while (leftover_frames > frames);
+
+ // src_data.end_of_input has to be checked to prevent infinite recursion
+ if (!src_data.end_of_input && c.has_flag(ProcessContext<float>::EndOfInput)) {
+ set_end_of_input (c);
+ }
+}
+
+void SampleRateConverter::set_end_of_input (ProcessContext<float> const & c)
+{
+ src_data.end_of_input = true;
+
+ float f;
+ ProcessContext<float> const dummy (c, &f, 0, channels);
+
+ /* No idea why this has to be done twice for all data to be written,
+ * but that just seems to be the way it is...
+ */
+ process (dummy);
+ process (dummy);
+}
+
+
+void SampleRateConverter::reset ()
+{
+ active = false;
+ max_frames_in = 0;
+ src_data.end_of_input = false;
+
+ if (src_state) {
+ src_delete (src_state);
+ }
+
+ leftover_frames = 0;
+ max_leftover_frames = 0;
+ if (leftover_data) {
+ free (leftover_data);
+ }
+
+ data_out_size = 0;
+ delete [] data_out;
+ data_out = 0;
+}
+
+} // namespace
diff --git a/libs/audiographer/src/utils.cc b/libs/audiographer/src/utils.cc
new file mode 100644
index 0000000000..018fad3113
--- /dev/null
+++ b/libs/audiographer/src/utils.cc
@@ -0,0 +1,14 @@
+#include "audiographer/utils.h"
+
+using namespace AudioGrapher;
+
+char const * Utils::zeros = 0;
+unsigned long Utils::num_zeros = 0;
+
+void
+Utils::free_resources()
+{
+ num_zeros = 0;
+ delete [] zeros;
+ zeros = 0;
+} \ No newline at end of file
diff --git a/libs/audiographer/tests/chunker_test.cc b/libs/audiographer/tests/chunker_test.cc
new file mode 100644
index 0000000000..ab0c04d7f9
--- /dev/null
+++ b/libs/audiographer/tests/chunker_test.cc
@@ -0,0 +1,112 @@
+#include "utils.h"
+#include "audiographer/chunker.h"
+
+#include <cassert>
+
+using namespace AudioGrapher;
+
+class ChunkerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (ChunkerTest);
+ CPPUNIT_TEST (testSynchronousProcess);
+ CPPUNIT_TEST (testAsynchronousProcess);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames);
+ sink.reset (new VectorSink<float>());
+ chunker.reset (new Chunker<float>(frames * 2));
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testSynchronousProcess()
+ {
+ chunker->add_output (sink);
+ nframes_t frames_output = 0;
+
+ ProcessContext<float> const context (random_data, frames, 1);
+
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
+
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames], frames));
+
+ sink->reset();
+
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
+
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames], frames));
+ }
+
+ void testAsynchronousProcess()
+ {
+ assert (frames % 2 == 0);
+
+ chunker->add_output (sink);
+ nframes_t frames_output = 0;
+
+ ProcessContext<float> const half_context (random_data, frames / 2, 1);
+ ProcessContext<float> const context (random_data, frames, 1);
+
+ // 0.5
+ chunker->process (half_context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
+
+ // 1.5
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
+
+ // 2.5
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames / 2));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames / 2], frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[ 3 * frames / 2], frames / 2));
+
+ sink->reset();
+
+ // 3.5
+ chunker->process (context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_output);
+
+ // 4.0
+ chunker->process (half_context);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (2 * frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_equals (&random_data[frames / 2], sink->get_array(), frames / 2));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[frames / 2], frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, &sink->get_array()[ 3 * frames / 2], frames / 2));
+ }
+
+ private:
+ boost::shared_ptr<Chunker<float> > chunker;
+ boost::shared_ptr<VectorSink<float> > sink;
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (ChunkerTest);
+
diff --git a/libs/audiographer/tests/deinterleaver_test.cc b/libs/audiographer/tests/deinterleaver_test.cc
new file mode 100644
index 0000000000..b0adbc0444
--- /dev/null
+++ b/libs/audiographer/tests/deinterleaver_test.cc
@@ -0,0 +1,133 @@
+#include "utils.h"
+#include "audiographer/deinterleaver.h"
+
+using namespace AudioGrapher;
+
+class DeInterleaverTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (DeInterleaverTest);
+ CPPUNIT_TEST (testUninitialized);
+ CPPUNIT_TEST (testInvalidOutputIndex);
+ CPPUNIT_TEST (testInvalidInputSize);
+ CPPUNIT_TEST (testOutputSize);
+ CPPUNIT_TEST (testZeroInput);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ channels = 3;
+ frames_per_channel = 128;
+ total_frames = channels * frames_per_channel;
+ random_data = TestUtils::init_random_data (total_frames, 1.0);
+
+ deinterleaver.reset (new DeInterleaver<float>());
+ sink_a.reset (new VectorSink<float>());
+ sink_b.reset (new VectorSink<float>());
+ sink_c.reset (new VectorSink<float>());
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testUninitialized()
+ {
+ deinterleaver.reset (new DeInterleaver<float>());
+ CPPUNIT_ASSERT_THROW (deinterleaver->output(0)->add_output (sink_a), Exception);
+ }
+
+ void testInvalidOutputIndex()
+ {
+ deinterleaver->init (3, frames_per_channel);
+ CPPUNIT_ASSERT_THROW (deinterleaver->output(3)->add_output (sink_a), Exception);
+ }
+
+ void testInvalidInputSize()
+ {
+ deinterleaver->init (channels, frames_per_channel);
+
+ ProcessContext<float> c (random_data, 0, channels);
+
+ // Too many, frames % channels == 0
+ c.frames() = total_frames + channels;
+ CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
+
+ // Too many, frames % channels != 0
+ c.frames() = total_frames + 1;
+ CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
+
+ // Too few, frames % channels != 0
+ c.frames() = total_frames - 1;
+ CPPUNIT_ASSERT_THROW (deinterleaver->process (c), Exception);
+ }
+
+ void assert_outputs (nframes_t expected_frames)
+ {
+ nframes_t generated_frames = 0;
+
+ generated_frames = sink_a->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+
+ generated_frames = sink_b->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+
+ generated_frames = sink_c->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+ }
+
+ void testOutputSize()
+ {
+ deinterleaver->init (channels, frames_per_channel);
+
+ deinterleaver->output (0)->add_output (sink_a);
+ deinterleaver->output (1)->add_output (sink_b);
+ deinterleaver->output (2)->add_output (sink_c);
+
+ // Test maximum frame input
+ ProcessContext<float> c (random_data, total_frames, channels);
+ deinterleaver->process (c);
+ assert_outputs (frames_per_channel);
+
+ // Now with less frames
+ nframes_t const less_frames = frames_per_channel / 4;
+ c.frames() = less_frames * channels;
+ deinterleaver->process (c);
+ assert_outputs (less_frames);
+ }
+
+ void testZeroInput()
+ {
+ deinterleaver->init (channels, frames_per_channel);
+
+ deinterleaver->output (0)->add_output (sink_a);
+ deinterleaver->output (1)->add_output (sink_b);
+ deinterleaver->output (2)->add_output (sink_c);
+
+ // Input zero frames
+ ProcessContext<float> c (random_data, 0, channels);
+ deinterleaver->process (c);
+
+ // ...and now test regular input
+ c.frames() = total_frames;
+ deinterleaver->process (c);
+ assert_outputs (frames_per_channel);
+ }
+
+
+ private:
+ boost::shared_ptr<DeInterleaver<float> > deinterleaver;
+
+ boost::shared_ptr<VectorSink<float> > sink_a;
+ boost::shared_ptr<VectorSink<float> > sink_b;
+ boost::shared_ptr<VectorSink<float> > sink_c;
+
+ float * random_data;
+ nframes_t frames_per_channel;
+ nframes_t total_frames;
+ unsigned int channels;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (DeInterleaverTest);
+
diff --git a/libs/audiographer/tests/identity_vertex_test.cc b/libs/audiographer/tests/identity_vertex_test.cc
new file mode 100644
index 0000000000..5a3ae7c9f2
--- /dev/null
+++ b/libs/audiographer/tests/identity_vertex_test.cc
@@ -0,0 +1,99 @@
+#include "utils.h"
+#include "audiographer/identity_vertex.h"
+
+using namespace AudioGrapher;
+
+class IdentityVertexTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (IdentityVertexTest);
+ CPPUNIT_TEST (testProcess);
+ CPPUNIT_TEST (testRemoveOutput);
+ CPPUNIT_TEST (testClearOutputs);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames);
+
+ zero_data = new float[frames];
+ memset (zero_data, 0, frames * sizeof(float));
+
+ sink_a.reset (new VectorSink<float>());
+ sink_b.reset (new VectorSink<float>());
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ delete [] zero_data;
+ }
+
+ void testProcess()
+ {
+ vertex.reset (new IdentityVertex<float>());
+ vertex->add_output (sink_a);
+ vertex->add_output (sink_b);
+
+ nframes_t frames_output = 0;
+
+ ProcessContext<float> c (random_data, frames, 1);
+ vertex->process (c);
+
+ frames_output = sink_a->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+
+ frames_output = sink_b->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_b->get_array(), frames));
+ }
+
+ void testRemoveOutput()
+ {
+ vertex.reset (new IdentityVertex<float>());
+ vertex->add_output (sink_a);
+ vertex->add_output (sink_b);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ vertex->process (c);
+
+ vertex->remove_output (sink_a);
+ ProcessContext<float> zc (zero_data, frames, 1);
+ vertex->process (zc);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (zero_data, sink_b->get_array(), frames));
+ }
+
+ void testClearOutputs()
+ {
+ vertex.reset (new IdentityVertex<float>());
+ vertex->add_output (sink_a);
+ vertex->add_output (sink_b);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ vertex->process (c);
+
+ vertex->clear_outputs ();
+ ProcessContext<float> zc (zero_data, frames, 1);
+ vertex->process (zc);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink_b->get_array(), frames));
+ }
+
+ private:
+ boost::shared_ptr<IdentityVertex<float> > vertex;
+ boost::shared_ptr<VectorSink<float> > sink_a;
+ boost::shared_ptr<VectorSink<float> > sink_b;
+
+ float * random_data;
+ float * zero_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (IdentityVertexTest);
+
diff --git a/libs/audiographer/tests/interleaver_deinterleaver_test.cc b/libs/audiographer/tests/interleaver_deinterleaver_test.cc
new file mode 100644
index 0000000000..5655253e62
--- /dev/null
+++ b/libs/audiographer/tests/interleaver_deinterleaver_test.cc
@@ -0,0 +1,120 @@
+#include "utils.h"
+#include "audiographer/interleaver.h"
+#include "audiographer/deinterleaver.h"
+
+using namespace AudioGrapher;
+
+class InterleaverDeInterleaverTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (InterleaverDeInterleaverTest);
+ CPPUNIT_TEST (testInterleavedInput);
+ CPPUNIT_TEST (testDeInterleavedInput);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ channels = 3;
+ frames_per_channel = 128;
+ total_frames = channels * frames_per_channel;
+
+ random_data_a = TestUtils::init_random_data (total_frames, 1.0);
+ random_data_b = TestUtils::init_random_data (frames_per_channel, 1.0);
+ random_data_c = TestUtils::init_random_data (frames_per_channel, 1.0);
+
+ deinterleaver.reset (new DeInterleaver<float>());
+ interleaver.reset (new Interleaver<float>());
+
+ sink_a.reset (new VectorSink<float>());
+ sink_b.reset (new VectorSink<float>());
+ sink_c.reset (new VectorSink<float>());
+ }
+
+ void tearDown()
+ {
+ delete [] random_data_a;
+ delete [] random_data_b;
+ delete [] random_data_c;
+ }
+
+ void testInterleavedInput()
+ {
+ deinterleaver->init (channels, frames_per_channel);
+ interleaver->init (channels, frames_per_channel);
+
+ deinterleaver->output (0)->add_output (interleaver->input (0));
+ deinterleaver->output (1)->add_output (interleaver->input (1));
+ deinterleaver->output (2)->add_output (interleaver->input (2));
+
+ interleaver->add_output (sink_a);
+
+ // Process and assert
+ ProcessContext<float> c (random_data_a, total_frames, channels);
+ deinterleaver->process (c);
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), total_frames));
+
+ // And a second round...
+ nframes_t less_frames = (frames_per_channel / 10) * channels;
+ c.frames() = less_frames;
+ deinterleaver->process (c);
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), less_frames));
+ }
+
+ void testDeInterleavedInput()
+ {
+ deinterleaver->init (channels, frames_per_channel);
+ interleaver->init (channels, frames_per_channel);
+
+ interleaver->add_output (deinterleaver);
+
+ deinterleaver->output (0)->add_output (sink_a);
+ deinterleaver->output (1)->add_output (sink_b);
+ deinterleaver->output (2)->add_output (sink_c);
+
+ ProcessContext<float> c_a (random_data_a, frames_per_channel, 1);
+ ProcessContext<float> c_b (random_data_b, frames_per_channel, 1);
+ ProcessContext<float> c_c (random_data_c, frames_per_channel, 1);
+
+ // Process and assert
+ interleaver->input (0)->process (c_a);
+ interleaver->input (1)->process (c_b);
+ interleaver->input (2)->process (c_c);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), frames_per_channel));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_b, sink_b->get_array(), frames_per_channel));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_c, sink_c->get_array(), frames_per_channel));
+
+ // And a second round...
+ nframes_t less_frames = frames_per_channel / 5;
+ c_a.frames() = less_frames;
+ c_b.frames() = less_frames;
+ c_c.frames() = less_frames;
+ interleaver->input (0)->process (c_a);
+ interleaver->input (1)->process (c_b);
+ interleaver->input (2)->process (c_c);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_a, sink_a->get_array(), less_frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_b, sink_b->get_array(), less_frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data_c, sink_c->get_array(), less_frames));
+
+ }
+
+ private:
+ boost::shared_ptr<Interleaver<float> > interleaver;
+ boost::shared_ptr<DeInterleaver<float> > deinterleaver;
+
+ boost::shared_ptr<VectorSink<float> > sink_a;
+ boost::shared_ptr<VectorSink<float> > sink_b;
+ boost::shared_ptr<VectorSink<float> > sink_c;
+
+ float * random_data_a;
+ float * random_data_b;
+ float * random_data_c;
+
+ nframes_t frames_per_channel;
+ nframes_t total_frames;
+ unsigned int channels;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (InterleaverDeInterleaverTest);
+
diff --git a/libs/audiographer/tests/interleaver_test.cc b/libs/audiographer/tests/interleaver_test.cc
new file mode 100644
index 0000000000..abe385699d
--- /dev/null
+++ b/libs/audiographer/tests/interleaver_test.cc
@@ -0,0 +1,132 @@
+#include "utils.h"
+#include "audiographer/interleaver.h"
+
+using namespace AudioGrapher;
+
+class InterleaverTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (InterleaverTest);
+ CPPUNIT_TEST (testUninitialized);
+ CPPUNIT_TEST (testInvalidInputIndex);
+ CPPUNIT_TEST (testInvalidInputSize);
+ CPPUNIT_TEST (testOutputSize);
+ CPPUNIT_TEST (testZeroInput);
+ CPPUNIT_TEST (testChannelSync);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ channels = 3;
+ frames = 128;
+ random_data = TestUtils::init_random_data (frames, 1.0);
+
+ interleaver.reset (new Interleaver<float>());
+ sink.reset (new VectorSink<float>());
+
+ interleaver->init (channels, frames);
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testUninitialized()
+ {
+ interleaver.reset (new Interleaver<float>());
+ ProcessContext<float> c (random_data, frames, 1);
+ CPPUNIT_ASSERT_THROW (interleaver->input(0)->process (c), Exception);
+ }
+
+ void testInvalidInputIndex()
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ CPPUNIT_ASSERT_THROW (interleaver->input (3)->process (c), Exception);
+ }
+
+ void testInvalidInputSize()
+ {
+ ProcessContext<float> c (random_data, frames + 1, 1);
+ CPPUNIT_ASSERT_THROW (interleaver->input (0)->process (c), Exception);
+
+ c.frames() = frames;
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ c.frames() = frames -1;
+ CPPUNIT_ASSERT_THROW (interleaver->input (2)->process (c), Exception);
+
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ c.frames() = frames;
+ CPPUNIT_ASSERT_THROW (interleaver->input (2)->process (c), Exception);
+ }
+
+ void testOutputSize()
+ {
+ interleaver->add_output (sink);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ interleaver->input (2)->process (c);
+
+ nframes_t expected_frames = frames * channels;
+ nframes_t generated_frames = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+
+ nframes_t less_frames = frames / 2;
+ c.frames() = less_frames;
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ interleaver->input (2)->process (c);
+
+ expected_frames = less_frames * channels;
+ generated_frames = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+ }
+
+ void testZeroInput()
+ {
+ interleaver->add_output (sink);
+
+ // input zero frames to all inputs
+ ProcessContext<float> c (random_data, 0, 1);
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ interleaver->input (2)->process (c);
+
+ // NOTE zero input is allowed to be a NOP
+
+ // ...now test regular input
+ c.frames() = frames;
+ interleaver->input (0)->process (c);
+ interleaver->input (1)->process (c);
+ interleaver->input (2)->process (c);
+
+ nframes_t expected_frames = frames * channels;
+ nframes_t generated_frames = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (expected_frames, generated_frames);
+ }
+
+ void testChannelSync()
+ {
+ interleaver->add_output (sink);
+ ProcessContext<float> c (random_data, frames, 1);
+ interleaver->input (0)->process (c);
+ CPPUNIT_ASSERT_THROW (interleaver->input (0)->process (c), Exception);
+ }
+
+
+ private:
+ boost::shared_ptr<Interleaver<float> > interleaver;
+
+ boost::shared_ptr<VectorSink<float> > sink;
+
+ nframes_t channels;
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (InterleaverTest);
+
diff --git a/libs/audiographer/tests/normalizer_test.cc b/libs/audiographer/tests/normalizer_test.cc
new file mode 100644
index 0000000000..711e0018ca
--- /dev/null
+++ b/libs/audiographer/tests/normalizer_test.cc
@@ -0,0 +1,60 @@
+#include "utils.h"
+
+#include "audiographer/normalizer.h"
+#include "audiographer/peak_reader.h"
+
+using namespace AudioGrapher;
+
+class NormalizerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (NormalizerTest);
+ CPPUNIT_TEST (testConstAmplify);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 1024;
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testConstAmplify()
+ {
+ float target = 0.0;
+ random_data = TestUtils::init_random_data(frames, 0.5);
+
+ normalizer.reset (new Normalizer(target));
+ peak_reader.reset (new PeakReader());
+ sink.reset (new VectorSink<float>());
+
+ ProcessContext<float> const c (random_data, frames, 1);
+ peak_reader->process (c);
+
+ float peak = peak_reader->get_peak();
+ normalizer->alloc_buffer (frames);
+ normalizer->set_peak (peak);
+ normalizer->add_output (sink);
+ normalizer->process (c);
+
+ peak_reader->reset();
+ ConstProcessContext<float> normalized (sink->get_array(), frames, 1);
+ peak_reader->process (normalized);
+
+ peak = peak_reader->get_peak();
+ CPPUNIT_ASSERT (-FLT_EPSILON <= (peak - 1.0) && (peak - 1.0) <= 0.0);
+ }
+
+ private:
+ boost::shared_ptr<Normalizer> normalizer;
+ boost::shared_ptr<PeakReader> peak_reader;
+ boost::shared_ptr<VectorSink<float> > sink;
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (NormalizerTest);
diff --git a/libs/audiographer/tests/peak_reader_test.cc b/libs/audiographer/tests/peak_reader_test.cc
new file mode 100644
index 0000000000..dce03b6caf
--- /dev/null
+++ b/libs/audiographer/tests/peak_reader_test.cc
@@ -0,0 +1,53 @@
+#include "utils.h"
+#include "audiographer/peak_reader.h"
+
+using namespace AudioGrapher;
+
+class PeakReaderTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (PeakReaderTest);
+ CPPUNIT_TEST (testProcess);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames);
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testProcess()
+ {
+ reader.reset (new PeakReader());
+ ProcessContext<float> c (random_data, frames, 1);
+
+ float peak = 1.5;
+ random_data[10] = peak;
+ reader->process (c);
+ CPPUNIT_ASSERT_EQUAL(peak, reader->get_peak());
+
+ peak = 2.0;
+ random_data[10] = peak;
+ reader->process (c);
+ CPPUNIT_ASSERT_EQUAL(peak, reader->get_peak());
+
+ peak = -2.1;
+ random_data[10] = peak;
+ reader->process (c);
+ float expected = fabs(peak);
+ CPPUNIT_ASSERT_EQUAL(expected, reader->get_peak());
+ }
+
+ private:
+ boost::shared_ptr<PeakReader> reader;
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (PeakReaderTest);
diff --git a/libs/audiographer/tests/sample_format_converter_test.cc b/libs/audiographer/tests/sample_format_converter_test.cc
new file mode 100644
index 0000000000..f723f7af53
--- /dev/null
+++ b/libs/audiographer/tests/sample_format_converter_test.cc
@@ -0,0 +1,209 @@
+#include "utils.h"
+#include "audiographer/sample_format_converter.h"
+
+using namespace AudioGrapher;
+
+class SampleFormatConverterTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (SampleFormatConverterTest);
+ CPPUNIT_TEST (testInit);
+ CPPUNIT_TEST (testFrameCount);
+ CPPUNIT_TEST (testFloat);
+ CPPUNIT_TEST (testInt32);
+ CPPUNIT_TEST (testInt24);
+ CPPUNIT_TEST (testInt16);
+ CPPUNIT_TEST (testUint8);
+ CPPUNIT_TEST (testChannelCount);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames, 1.0);
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testInit()
+ {
+ boost::shared_ptr<SampleFormatConverter<float> > f_converter (new SampleFormatConverter<float>(1));
+ f_converter->init (frames, D_Tri, 32); // Doesn't throw
+ CPPUNIT_ASSERT_THROW (f_converter->init (frames, D_Tri, 24), Exception);
+ CPPUNIT_ASSERT_THROW (f_converter->init (frames, D_Tri, 48), Exception);
+
+ boost::shared_ptr<SampleFormatConverter<int32_t> > i_converter (new SampleFormatConverter<int32_t>(1));
+ i_converter->init (frames, D_Tri, 32); // Doesn't throw
+ i_converter->init (frames, D_Tri, 24); // Doesn't throw
+ CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 8), Exception);
+ CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 16), Exception);
+ CPPUNIT_ASSERT_THROW (i_converter->init (frames, D_Tri, 48), Exception);
+
+ boost::shared_ptr<SampleFormatConverter<int16_t> > i16_converter (new SampleFormatConverter<int16_t>(1));
+ i16_converter->init (frames, D_Tri, 16); // Doesn't throw
+ CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 8), Exception);
+ CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 32), Exception);
+ CPPUNIT_ASSERT_THROW (i16_converter->init (frames, D_Tri, 48), Exception);
+
+ boost::shared_ptr<SampleFormatConverter<uint8_t> > ui_converter (new SampleFormatConverter<uint8_t>(1));
+ ui_converter->init (frames, D_Tri, 8); // Doesn't throw
+ CPPUNIT_ASSERT_THROW (ui_converter->init (frames, D_Tri, 4), Exception);
+ CPPUNIT_ASSERT_THROW (ui_converter->init (frames, D_Tri, 16), Exception);
+ }
+
+ void testFrameCount()
+ {
+ boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
+ boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
+
+ converter->init (frames, D_Tri, 32);
+ converter->add_output (sink);
+ nframes_t frames_output = 0;
+
+ {
+ ProcessContext<float> pc(random_data, frames / 2, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames / 2, frames_output);
+ }
+
+ {
+ ProcessContext<float> pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ }
+
+ {
+ ProcessContext<float> pc(random_data, frames + 1, 1);
+ CPPUNIT_ASSERT_THROW(converter->process (pc), Exception);
+ }
+ }
+
+ void testFloat()
+ {
+ boost::shared_ptr<SampleFormatConverter<float> > converter (new SampleFormatConverter<float>(1));
+ boost::shared_ptr<VectorSink<float> > sink (new VectorSink<float>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 32);
+ converter->add_output (sink);
+
+ converter->set_clip_floats (false);
+ ProcessContext<float> const pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_equals(sink->get_array(), random_data, frames));
+
+ // Make sure a few samples are < -1.0 and > 1.0
+ random_data[10] = -1.5;
+ random_data[20] = 1.5;
+
+ converter->set_clip_floats (true);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
+
+ for (nframes_t i = 0; i < frames; ++i) {
+ // fp comparison needs a bit of tolerance, 1.01 << 1.5
+ CPPUNIT_ASSERT(sink->get_data()[i] < 1.01);
+ CPPUNIT_ASSERT(sink->get_data()[i] > -1.01);
+ }
+ }
+
+ void testInt32()
+ {
+ boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
+ boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 32);
+ converter->add_output (sink);
+
+ ProcessContext<float> pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
+ }
+
+ void testInt24()
+ {
+ boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(1));
+ boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 24);
+ converter->add_output (sink);
+
+ ProcessContext<float> pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
+ }
+
+ void testInt16()
+ {
+ boost::shared_ptr<SampleFormatConverter<int16_t> > converter (new SampleFormatConverter<int16_t>(1));
+ boost::shared_ptr<VectorSink<int16_t> > sink (new VectorSink<int16_t>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 16);
+ converter->add_output (sink);
+
+ ProcessContext<float> pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
+ }
+
+ void testUint8()
+ {
+ boost::shared_ptr<SampleFormatConverter<uint8_t> > converter (new SampleFormatConverter<uint8_t>(1));
+ boost::shared_ptr<VectorSink<uint8_t> > sink (new VectorSink<uint8_t>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 8);
+ converter->add_output (sink);
+
+ ProcessContext<float> pc(random_data, frames, 1);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), frames));
+ }
+
+ void testChannelCount()
+ {
+ boost::shared_ptr<SampleFormatConverter<int32_t> > converter (new SampleFormatConverter<int32_t>(3));
+ boost::shared_ptr<VectorSink<int32_t> > sink (new VectorSink<int32_t>());
+ nframes_t frames_output = 0;
+
+ converter->init(frames, D_Tri, 32);
+ converter->add_output (sink);
+
+ ProcessContext<float> pc(random_data, 4, 1);
+ CPPUNIT_ASSERT_THROW (converter->process (pc), Exception);
+
+ pc.frames() = frames - (frames % 3);
+ converter->process (pc);
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (pc.frames(), frames_output);
+ CPPUNIT_ASSERT (TestUtils::array_filled(sink->get_array(), pc.frames()));
+ }
+
+ private:
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (SampleFormatConverterTest);
+
diff --git a/libs/audiographer/tests/silence_trimmer_test.cc b/libs/audiographer/tests/silence_trimmer_test.cc
new file mode 100644
index 0000000000..16234bec37
--- /dev/null
+++ b/libs/audiographer/tests/silence_trimmer_test.cc
@@ -0,0 +1,196 @@
+#include "utils.h"
+
+#include "audiographer/silence_trimmer.h"
+
+using namespace AudioGrapher;
+
+class SilenceTrimmerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (SilenceTrimmerTest);
+ CPPUNIT_TEST (testExceptions);
+ CPPUNIT_TEST (testFullBuffers);
+ CPPUNIT_TEST (testPartialBuffers);
+ CPPUNIT_TEST (testAddSilenceBeginning);
+ CPPUNIT_TEST (testAddSilenceEnd);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+
+ random_data = TestUtils::init_random_data(frames);
+ random_data[0] = 0.5;
+ random_data[frames - 1] = 0.5;
+
+ zero_data = new float[frames];
+ memset(zero_data, 0, frames * sizeof(float));
+
+ half_random_data = TestUtils::init_random_data(frames);
+ memset(half_random_data, 0, (frames / 2) * sizeof(float));
+
+ trimmer.reset (new SilenceTrimmer<float>());
+ sink.reset (new AppendingVectorSink<float>());
+
+ trimmer->set_trim_beginning (true);
+ trimmer->set_trim_end (true);
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ delete [] zero_data;
+ delete [] half_random_data;
+
+ AudioGrapher::Utils::free_resources();
+ }
+
+ void testFullBuffers()
+ {
+ trimmer->add_output (sink);
+ AudioGrapher::Utils::init_zeros<float>(frames / 2);
+
+ {
+ ProcessContext<float> c (zero_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL ((nframes_t) 0, frames_processed);
+ }
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_processed);
+ CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
+ }
+
+ {
+ ProcessContext<float> c (zero_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_processed);
+ }
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (3 * frames, frames_processed);
+ CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames], zero_data, frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[2 * frames], random_data, frames));
+ }
+
+ {
+ ProcessContext<float> c (zero_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (3 * frames, frames_processed);
+ }
+ }
+
+ void testPartialBuffers()
+ {
+ trimmer->add_output (sink);
+ AudioGrapher::Utils::init_zeros<float>(frames / 4);
+
+ {
+ ProcessContext<float> c (half_random_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames / 2, frames_processed);
+ CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), &half_random_data[frames / 2], frames / 2));
+ }
+
+ {
+ ProcessContext<float> c (zero_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames / 2, frames_processed);
+ }
+
+ {
+ ProcessContext<float> c (half_random_data, frames, 1);
+ trimmer->process (c);
+ nframes_t frames_processed = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (2 * frames + frames / 2, frames_processed);
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames + frames / 2], half_random_data, frames));
+ }
+ }
+
+ void testExceptions()
+ {
+ // TODO more tests here
+
+ trimmer->add_output (sink);
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ trimmer->process (c);
+ }
+
+ {
+ ProcessContext<float> c (zero_data, frames, 1);
+ trimmer->process (c);
+ }
+
+ {
+ // Zeros not inited, so this should throw
+ ProcessContext<float> c (random_data, frames, 1);
+ CPPUNIT_ASSERT_THROW (trimmer->process (c), Exception);
+ }
+ }
+
+ void testAddSilenceBeginning()
+ {
+ trimmer->add_output (sink);
+ AudioGrapher::Utils::init_zeros<float>(frames / 2);
+
+ nframes_t silence = frames / 2;
+ trimmer->add_silence_to_beginning (silence);
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ trimmer->process (c);
+ }
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), zero_data, silence));
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[silence], random_data, frames));
+ }
+
+ void testAddSilenceEnd()
+ {
+ trimmer->add_output (sink);
+ AudioGrapher::Utils::init_zeros<float>(frames / 2);
+
+ nframes_t silence = frames / 3;
+ trimmer->add_silence_to_end (silence);
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ trimmer->process (c);
+ }
+
+ {
+ ProcessContext<float> c (random_data, frames, 1);
+ c.set_flag (ProcessContext<float>::EndOfInput);
+ trimmer->process (c);
+ }
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (sink->get_array(), random_data, frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames], random_data, frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals (&sink->get_array()[frames * 2], zero_data, silence));
+ }
+
+ private:
+ boost::shared_ptr<SilenceTrimmer<float> > trimmer;
+ boost::shared_ptr<AppendingVectorSink<float> > sink;
+
+ float * random_data;
+ float * zero_data;
+ float * half_random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (SilenceTrimmerTest);
diff --git a/libs/audiographer/tests/sndfile_writer_test.cc b/libs/audiographer/tests/sndfile_writer_test.cc
new file mode 100644
index 0000000000..359d456f15
--- /dev/null
+++ b/libs/audiographer/tests/sndfile_writer_test.cc
@@ -0,0 +1,42 @@
+#include "utils.h"
+#include "audiographer/sndfile_writer.h"
+
+using namespace AudioGrapher;
+
+class SndfileWriterTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (SndfileWriterTest);
+ CPPUNIT_TEST (testProcess);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames);
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testProcess()
+ {
+ uint channels = 2;
+ std::string filename ("test.wav");
+ writer.reset (new SndfileWriter<float>(channels, 44100, SF_FORMAT_WAV | SF_FORMAT_FLOAT, filename));
+ ProcessContext<float> c (random_data, frames, channels);
+ c.set_flag (ProcessContext<float>::EndOfInput);
+ writer->process (c);
+ }
+
+ private:
+ boost::shared_ptr<SndfileWriter<float> > writer;
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (SndfileWriterTest);
+
diff --git a/libs/audiographer/tests/sr_converter_test.cc b/libs/audiographer/tests/sr_converter_test.cc
new file mode 100644
index 0000000000..59c05806c6
--- /dev/null
+++ b/libs/audiographer/tests/sr_converter_test.cc
@@ -0,0 +1,101 @@
+#include "utils.h"
+#include "audiographer/sr_converter.h"
+
+using namespace AudioGrapher;
+
+class SampleRateConverterTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (SampleRateConverterTest);
+ CPPUNIT_TEST (testNoConversion);
+ CPPUNIT_TEST (testUpsampleLength);
+ CPPUNIT_TEST (testDownsampleLength);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data(frames);
+ sink.reset (new AppendingVectorSink<float>());
+ converter.reset (new SampleRateConverter (1));
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ }
+
+ void testNoConversion()
+ {
+ assert (frames % 2 == 0);
+ nframes_t const half_frames = frames / 2;
+ nframes_t frames_output = 0;
+
+ converter->init (44100, 44100);
+ converter->add_output (sink);
+
+ ProcessContext<float> c (random_data, half_frames, 1);
+ converter->process (c);
+ ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
+ c2.set_flag (ProcessContext<float>::EndOfInput);
+ converter->process (c2);
+
+ frames_output = sink->get_data().size();
+ CPPUNIT_ASSERT_EQUAL (frames, frames_output);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals (random_data, sink->get_array(), frames));
+ }
+
+ void testUpsampleLength()
+ {
+ assert (frames % 2 == 0);
+ nframes_t const half_frames = frames / 2;
+ nframes_t frames_output = 0;
+
+ converter->init (44100, 88200);
+ converter->allocate_buffers (half_frames);
+ converter->add_output (sink);
+
+ ProcessContext<float> c (random_data, half_frames, 1);
+ converter->process (c);
+ ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
+ c2.set_flag (ProcessContext<float>::EndOfInput);
+ converter->process (c2);
+
+ frames_output = sink->get_data().size();
+ nframes_t tolerance = 3;
+ CPPUNIT_ASSERT (2 * frames - tolerance < frames_output && frames_output < 2 * frames + tolerance);
+ }
+
+ void testDownsampleLength()
+ {
+ assert (frames % 2 == 0);
+ nframes_t const half_frames = frames / 2;
+ nframes_t frames_output = 0;
+
+ converter->init (88200, 44100);
+ converter->allocate_buffers (half_frames);
+ converter->add_output (sink);
+
+ ProcessContext<float> c (random_data, half_frames, 1);
+ converter->process (c);
+ ProcessContext<float> c2 (&random_data[half_frames], half_frames, 1);
+ c2.set_flag (ProcessContext<float>::EndOfInput);
+ converter->process (c2);
+
+ frames_output = sink->get_data().size();
+ nframes_t tolerance = 3;
+ CPPUNIT_ASSERT (half_frames - tolerance < frames_output && frames_output < half_frames + tolerance);
+ }
+
+
+ private:
+ boost::shared_ptr<SampleRateConverter > converter;
+ boost::shared_ptr<AppendingVectorSink<float> > sink;
+
+ float * random_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (SampleRateConverterTest);
+
diff --git a/libs/audiographer/tests/test_runner.cc b/libs/audiographer/tests/test_runner.cc
new file mode 100644
index 0000000000..6fed393dc8
--- /dev/null
+++ b/libs/audiographer/tests/test_runner.cc
@@ -0,0 +1,14 @@
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+
+#include <glibmm/thread.h>
+
+int main()
+{
+ Glib::thread_init();
+ CppUnit::TextUi::TestRunner runner;
+ CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
+ runner.addTest (registry.makeTest());
+ runner.run();
+ return 0;
+}
diff --git a/libs/audiographer/tests/threader_test.cc b/libs/audiographer/tests/threader_test.cc
new file mode 100644
index 0000000000..ac5588d79c
--- /dev/null
+++ b/libs/audiographer/tests/threader_test.cc
@@ -0,0 +1,155 @@
+#include "utils.h"
+#include "audiographer/threader.h"
+
+using namespace AudioGrapher;
+
+class ThreaderTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE (ThreaderTest);
+ CPPUNIT_TEST (testProcess);
+ CPPUNIT_TEST (testRemoveOutput);
+ CPPUNIT_TEST (testClearOutputs);
+ CPPUNIT_TEST (testExceptions);
+ CPPUNIT_TEST_SUITE_END ();
+
+ public:
+ void setUp()
+ {
+ frames = 128;
+ random_data = TestUtils::init_random_data (frames, 1.0);
+
+ zero_data = new float[frames];
+ memset (zero_data, 0, frames * sizeof(float));
+
+ thread_pool = new Glib::ThreadPool (3);
+ threader.reset (new Threader<float> (*thread_pool));
+
+ sink_a.reset (new VectorSink<float>());
+ sink_b.reset (new VectorSink<float>());
+ sink_c.reset (new VectorSink<float>());
+ sink_d.reset (new VectorSink<float>());
+ sink_e.reset (new VectorSink<float>());
+ sink_f.reset (new VectorSink<float>());
+
+ throwing_sink.reset (new ThrowingSink<float>());
+ }
+
+ void tearDown()
+ {
+ delete [] random_data;
+ delete [] zero_data;
+
+ thread_pool->shutdown();
+ delete thread_pool;
+ }
+
+ void testProcess()
+ {
+ threader->add_output (sink_a);
+ threader->add_output (sink_b);
+ threader->add_output (sink_c);
+ threader->add_output (sink_d);
+ threader->add_output (sink_e);
+ threader->add_output (sink_f);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ threader->process (c);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_d->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
+ }
+
+ void testRemoveOutput()
+ {
+ threader->add_output (sink_a);
+ threader->add_output (sink_b);
+ threader->add_output (sink_c);
+ threader->add_output (sink_d);
+ threader->add_output (sink_e);
+ threader->add_output (sink_f);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ threader->process (c);
+
+ // Remove a, b and f
+ threader->remove_output (sink_a);
+ threader->remove_output (sink_b);
+ threader->remove_output (sink_f);
+
+ ProcessContext<float> zc (zero_data, frames, 1);
+ threader->process (zc);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_c->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_d->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(zero_data, sink_e->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
+ }
+
+ void testClearOutputs()
+ {
+ threader->add_output (sink_a);
+ threader->add_output (sink_b);
+ threader->add_output (sink_c);
+ threader->add_output (sink_d);
+ threader->add_output (sink_e);
+ threader->add_output (sink_f);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ threader->process (c);
+
+ threader->clear_outputs();
+ ProcessContext<float> zc (zero_data, frames, 1);
+ threader->process (zc);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_d->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_f->get_array(), frames));
+ }
+
+ void testExceptions()
+ {
+ threader->add_output (sink_a);
+ threader->add_output (sink_b);
+ threader->add_output (sink_c);
+ threader->add_output (throwing_sink);
+ threader->add_output (sink_e);
+ threader->add_output (throwing_sink);
+
+ ProcessContext<float> c (random_data, frames, 1);
+ CPPUNIT_ASSERT_THROW (threader->process (c), Exception);
+
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_a->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_b->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_c->get_array(), frames));
+ CPPUNIT_ASSERT (TestUtils::array_equals(random_data, sink_e->get_array(), frames));
+ }
+
+ private:
+ Glib::ThreadPool * thread_pool;
+
+ boost::shared_ptr<Threader<float> > threader;
+ boost::shared_ptr<VectorSink<float> > sink_a;
+ boost::shared_ptr<VectorSink<float> > sink_b;
+ boost::shared_ptr<VectorSink<float> > sink_c;
+ boost::shared_ptr<VectorSink<float> > sink_d;
+ boost::shared_ptr<VectorSink<float> > sink_e;
+ boost::shared_ptr<VectorSink<float> > sink_f;
+
+ boost::shared_ptr<ThrowingSink<float> > throwing_sink;
+
+ float * random_data;
+ float * zero_data;
+ nframes_t frames;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION (ThreaderTest);
+
diff --git a/libs/audiographer/tests/utils.h b/libs/audiographer/tests/utils.h
new file mode 100644
index 0000000000..6b6a1fea0f
--- /dev/null
+++ b/libs/audiographer/tests/utils.h
@@ -0,0 +1,119 @@
+#ifndef AUDIOGRAPHER_TESTS_UTILS_H
+#define AUDIOGRAPHER_TESTS_UTILS_H
+
+// Includes we want almost always
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <boost/shared_ptr.hpp>
+
+// includes used in this file
+
+#include "audiographer/sink.h"
+#include "audiographer/exception.h"
+
+#include <vector>
+#include <cstring>
+#include <cstdlib>
+#include <ctime>
+
+using AudioGrapher::nframes_t;
+
+struct TestUtils
+{
+ template<typename T>
+ static bool array_equals (T const * a, T const * b, nframes_t frames)
+ {
+ for (nframes_t i = 0; i < frames; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template<typename T>
+ static bool array_filled (T const * array, nframes_t frames)
+ {
+ for (nframes_t i = 0; i < frames; ++i) {
+ if (array[i] == static_cast<T> (0.0)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /// Generate random data, all samples guaranteed not to be 0.0, 1.0 or -1.0
+ static float * init_random_data (nframes_t frames, float range = 1.0)
+ {
+ unsigned int const granularity = 4096;
+ float * data = new float[frames];
+ srand (std::time (NULL));
+
+ for (nframes_t i = 0; i < frames; ++i) {
+ do {
+ int biased_int = (rand() % granularity) - (granularity / 2);
+ data[i] = (range * biased_int) / granularity;
+ } while (data[i] == 0.0 || data[i] == 1.0 || data[i] == -1.0);
+ }
+ return data;
+ }
+};
+
+template<typename T>
+class VectorSink : public AudioGrapher::Sink<T>
+{
+ public:
+ virtual void process (AudioGrapher::ProcessContext<T> const & c)
+ {
+ data.resize (c.frames());
+ memcpy (&data[0], c.data(), c.frames() * sizeof(T));
+ }
+
+ void process (AudioGrapher::ProcessContext<T> & c) { AudioGrapher::Sink<T>::process (c); }
+ using AudioGrapher::Sink<T>::process;
+
+ std::vector<T> const & get_data() const { return data; }
+ T const * get_array() const { return &data[0]; }
+ void reset() { data.clear(); }
+
+ protected:
+ std::vector<T> data;
+
+};
+
+template<typename T>
+class AppendingVectorSink : public VectorSink<T>
+{
+ public:
+ void process (AudioGrapher::ProcessContext<T> const & c)
+ {
+ std::vector<T> & data (VectorSink<T>::data);
+ data.resize (total_frames + c.frames());
+ memcpy (&data[total_frames], c.data(), c.frames() * sizeof(T));
+ total_frames += c.frames();
+ }
+ using AudioGrapher::Sink<T>::process;
+
+ void reset ()
+ {
+ total_frames = 0;
+ VectorSink<T>::reset();
+ }
+
+ private:
+ nframes_t total_frames;
+};
+
+
+template<typename T>
+class ThrowingSink : public AudioGrapher::Sink<T>
+{
+ public:
+ void process (AudioGrapher::ProcessContext<T> const &)
+ {
+ throw AudioGrapher::Exception(*this, "ThrowingSink threw!");
+ }
+ using AudioGrapher::Sink<T>::process;
+};
+
+#endif // AUDIOGRAPHER_TESTS_UTILS_H
diff --git a/libs/audiographer/wscript b/libs/audiographer/wscript
new file mode 100644
index 0000000000..ed8388f580
--- /dev/null
+++ b/libs/audiographer/wscript
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import autowaf
+
+# Version of this package (even if built as a child)
+AUDIOGRAPHER_VERSION = '0.0.0'
+
+# Library version (UNIX style major, minor, micro)
+# major increment <=> incompatible changes
+# minor increment <=> compatible changes (additions)
+# micro increment <=> no interface changes
+# Version history:
+# 0.0.0 = 0,0,0
+AUDIOGRAPHER_LIB_VERSION = '0.0.0'
+
+# Variables for 'waf dist'
+APPNAME = 'audiographer'
+VERSION = AUDIOGRAPHER_VERSION
+
+# Mandatory variables
+srcdir = '.'
+blddir = 'build'
+
+def set_options(opt):
+ autowaf.set_options(opt)
+
+def configure(conf):
+ autowaf.configure(conf)
+
+ conf.check_tool('compiler_cxx')
+
+ autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
+ autowaf.check_pkg(conf, 'sigc++-2.0', uselib_store='SIGCPP', atleast_version='2.0', mandatory=False)
+ autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2', mandatory=False)
+ autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0', mandatory=False)
+ autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0', mandatory=False)
+ autowaf.check_pkg(conf, 'samplerate', uselib_store='SAMPLERATE', atleast_version='0.1.7', mandatory=False)
+ autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', atleast_version='1.0.18', mandatory=False)
+
+ # Boost headers
+ autowaf.check_header(conf, 'boost/shared_ptr.hpp')
+ autowaf.check_header(conf, 'boost/format.hpp')
+
+def build(bld):
+
+ # Headers
+ #bld.install_files('${INCLUDEDIR}/audiographer', 'audiographer/*.h')
+
+ bld.env['HAVE_ALL_GTHREAD'] = bld.env['HAVE_GLIB'] and bld.env['HAVE_GLIBMM'] and bld.env['HAVE_GTHREAD']
+
+ audiographer = bld.new_task_gen('cxx', 'shlib')
+ audiographer.source = '''
+ src/gdither/gdither.cc
+ src/sample_format_converter.cc
+ src/routines.cc
+ src/utils.cc
+ '''
+
+ if bld.env['HAVE_SNDFILE']:
+ audiographer.source += '''
+ src/sndfile_base.cc
+ src/sndfile_writer.cc
+ src/sndfile_reader.cc
+ '''
+
+ if bld.env['HAVE_SAMPLERATE']:
+ audiographer.source += '''
+ src/sr_converter.cc
+ '''
+
+ audiographer.name = 'libaudiographer'
+ audiographer.target = 'audiographer'
+ audiographer.export_incdirs = ['.', './src']
+ audiographer.includes = ['.', './src']
+ audiographer.uselib = 'GLIB GLIBMM GTHREAD SAMPLERATE SNDFILE'
+ audiographer.vnum = AUDIOGRAPHER_LIB_VERSION
+ audiographer.install_path = '${LIBDIR}'
+
+
+ if bld.env['BUILD_TESTS'] and bld.env['HAVE_CPPUNIT']:
+ # Unit tests
+ obj = bld.new_task_gen('cxx', 'program')
+ obj.source = '''
+ tests/identity_vertex_test.cc
+ tests/interleaver_test.cc
+ tests/deinterleaver_test.cc
+ tests/interleaver_deinterleaver_test.cc
+ tests/chunker_test.cc
+ tests/sample_format_converter_test.cc
+ tests/test_runner.cc
+ tests/peak_reader_test.cc
+ tests/normalizer_test.cc
+ tests/silence_trimmer_test.cc
+ '''
+
+ if bld.env['HAVE_ALL_GTHREAD']:
+ obj.source += '''
+ tests/threader_test.cc
+ '''
+
+ if bld.env['HAVE_SNDFILE']:
+ obj.source += '''
+ tests/sndfile_writer_test.cc
+ '''
+
+ if bld.env['HAVE_SAMPLERATE']:
+ obj.source += '''
+ tests/sr_converter_test.cc
+ '''
+
+ obj.uselib_local = 'libaudiographer'
+ obj.uselib = 'CPPUNIT GLIBMM'
+ obj.target = 'run-tests'
+ obj.install_path = ''
+
+def shutdown():
+ autowaf.shutdown()
+