Thu, 31 Aug 2006
libsndfile 1.0.17.
I've just released a new version of libsndfile. The changes are:
- Add a C++ header file which acts as a wrapper around libsndfile's C API.
- Fix the documentation which describes using the precompiled windows DLL with evil ware compilers.
- Minor bug fixes and cleanups.
Posted at: 22:19 | Category: CodeHacking/libsndfile | Permalink
Thu, 10 Aug 2006
C++ Wrapper for libsndfile, Part 4.
I got some email from Jaq who said:
"You don't actually give a good reason for not wanting a close() method on SndfileHandle; your reasons for not wanting open() are good, but surely a handle needs a way of closing itself? Or does the API overload object deletion with the closing of the handle too?"
So first of all, I should have stated that my current candidate header file is available here. The SndfileHandle object is designed to be used with the Resource Acquisition Is Initialization pattern. In particular, the object will close the file and release all allocations when it goes out of scope. For instance:
{
SndfileHandle file ("foo.wav") ;
// Do something with file which gets closed automatically
// when file goes out of scope.
}
So what's the problem with the close() method? Well, its very similar to the problems with the open() method. Lets look at an example:
SndfileHandle file1 ("foo.wav") ;
// Make file2 == file1
SndfileHandle file2 = file1 ;
// Close file1
file1.close ("bar.wav") ;
Obviously, the handle associated with file1 should be closed, but what about file2? Should that be closed or remain open?
The fact that its not obvious means that its best left out. If anyone really wants to make sure that a SndfileHandle is closed they can do:
SndfileHandle file1 ("foo.wav") ;
// Make file2 == file1
SndfileHandle file2 = file1 ;
// Close file1
file1 = SndfileHandle () ;
In this case its a little more obvious that file1 and file2 now refer to different handles.
Posted at: 20:54 | Category: CodeHacking/libsndfile | Permalink
Wed, 09 Aug 2006
C++ Wrapper for libsndfile, Part 3.
Thinking about the C++ wrapper continues. In the current candidate version, a SndfileHandle contains a pointer to a private reference counted struct which contains the actual data. The SndfileHandle class also has a copy constructor and an assignment operator.
In addition to the above, a number of people on the mailing list have asked for the SndfileHandle class to have open() and close() methods. This seems reasonable on the face of it, but Daniel Schmitt points out that the combination of copy/assign and open/close results in a rather strange ambiguity.
Daniel gives the following example using copy/assign (ie no open/close methods):
SndfileHandle file1 ("foo.wav") ;
// Make file2 == file1
SndfileHandle file2 = file1 ;
// Now reuse file1
file1 = SndfileHandle("bar.wav");
At the end of that block of code we now have file1 and file2 operating on different handles, which is exactly what any reasonable programmer would expect.
Now look at what happens if we have open/close methods:
SndfileHandle file1 ("foo.wav") ;
// Make file2 == file1
SndfileHandle file2 = file1 ;
// Now reuse file1
file1.open ("bar.wav") ;
The open method can be implemented in one of the following two ways :
- Decrease the reference count on what file1 used to operate on and then initialize a one.
- Modify the data that both SndfileHandles point to.
After the block of code above, the two different implementations would result in the following state :
- file1 and file2 refer to different handles
- file2 now refers to "bar.wav" which is even worse
Obviously, the second implementation is completely wrong, but the first implementation is at least questionable. In terms of providing something which balances utility and consistency I'm tending to favor the idea of keeping the copy/assign operations and not providing open/close methods.
Posted at: 20:20 | Category: CodeHacking/libsndfile | Permalink
Mon, 31 Jul 2006
C++ Wrapper for libsndfile, Part 2.
After yesterday's blog post a guy in Germany, Daniel Schmitt, piped up on the libsndfile mailing lists and insisted I reconsider the C++ wrapper class' copy/assign issue. My big problem with copy/assign were that they would not behave the way people might reasonable expect them to.
Daniel's major contribution was renaming the class from Sndfile to SndfileHandle. Once that is done, having a copy constructor and an assignment operator using reference counting makes sense. With the class name containing the word "Handle", the name now fits the behavior. This is such a minor and seemingly trivial change but I simply didn't see it.
Thanks Daniel. Brilliant!
Posted at: 19:48 | Category: CodeHacking/libsndfile | Permalink
Sun, 30 Jul 2006
A C++ Wrapper for libsndfile.
Over the years I've received a bunch emails saying stuff like "why did you write libsndfile in that old fashioned C language instead of nice modern shiny C++?". Obviously anyone who even thinks something like this is too ignorant of C to be a good C++ programmer. A competent C++ programmer needs to know and be comfortable with the whole of the C language as well as the whole of C++.
At the time I started work on libsndfile in 1998 I was writing far more C++ code than C code. However, back then, the GNU C++ compiler was nowhere near as good as it is today and I thought a C library interface was a safer bet than C++. In retrospect, I believe the decision of using C was spot on, for the following reasons:
- The GNU C++ ABI (Application Binary Interface) has changed at least two times since 1998. The C ABI hasn't changed once in the same time frame.
- The level of C++ support by the GNU C++ compiler has been changing so much that using C++ for libsndfile would have been a huge pain. For instance, code that compiles without warnings with g++-3.3 can spit out a huge number of warnings for any later version of the compiler.
- Since 1999, the use of other high level languages has blossomed. Writing wrappers for these high level languages around C libraries is usually significantly easier than writing wrappers for C++ libraries.
However, some people do prefer C++ to C and many of those would probably be writing their own C++ wrapper. Since the vast majority of these wrappers would largely the same, it makes sense for me to distribute a C++ wrapper with libsndfile.
I decided on the following set of criteria for the wrapper:
- It must be light weight. The wrapper should be a single header file with all methods being inlined.
- It should not depend on iostream.
- It should avoid the Standard Template Library.
It does however use templates for the read/write/readf/writef methods:
template <typename T> sf_count_t read (T *ptr, sf_count_t items) ; template <typename T> sf_count_t readf (T *ptr, sf_count_t frames) ; template <typename T> sf_count_t write (const T *ptr, sf_count_t items) ; template <typename T> sf_count_t writef (const T *ptr, sf_count_t frames) ;
with explicit specializations for types short, int, float and double.
It also explicitly makes the copy constructor and assignment operator private. The problem with these two is that two objects wrapping the same SNDFILE* pointer will not give the expected behavior. With the C version:
SNDFILE *file1 = sf_open (filename, ...); SNDFILE *file2 = file1 ;
anyone reading the code can see that file1 and file2 are two pointers pointing to the same object. The code reader knows what behavior to expect here.
Now contrast this with the C++ version:
Sndfile file1 (filename, ...) ; Sndfile file2 (file1) ;
The objects file1 and file2 look like two independent objects and should behave like two independent objects, but instead they behave like they do in the C version above. I believe that this is inconsistent.
The only solution that would maintain consistency would be to make the copy constructor do a deep copy but that is simply too much of a pain in the neck to implement.
The current version of the wrapper is available here or in verision 1.0.17 or later of libsndfile.
Posted at: 18:26 | Category: CodeHacking/libsndfile | Permalink