diff options
Diffstat (limited to 'libs/audiographer')
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 ®istry = 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() + |