Open source high-quality pro audio sample rate converter library released from Voxengo

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

32-bit floats are still useful on ARM since there doubles are twice as expensive.

It's up to you whether or not to put a .NET port.
Image

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Hello Aleksey,

I've been doing well with your wonderful samplerate converter, and getting ready for an upcoming release of our framework that uses r8brain for conversion on Windows. However, I'm hitting a snag when the CDSPResampler is destroyed at shutdown. Specifcally, CSyncObject::aquire() is consistently failing on the call to EnterCriticalSection() due to an access violation.

It is diffult for me to understand why this is failing at shutdown, however I can destroy the resamplers at runtime without any problems (and also no issues on mac, but that is most likely because memory usage is a bit more lax there). Does the following stack trace indicate any clues to you? I'm using v1.5.

Code: Select all (#)

>	DeviceTest.exe!r8b::CSyncObject::acquire() Line 599	C++
 	DeviceTest.exe!r8b::CSyncKeeper::CSyncKeeper(r8b::CSyncObject & aSyncObj) Line 673	C++
 	DeviceTest.exe!r8b::CDSPFIRFilter::unref() Line 646	C++
 	DeviceTest.exe!r8b::CDSPBlockConvolver::~CDSPBlockConvolver() Line 109	C++
 	DeviceTest.exe!r8b::CDSPBlockConvolver::`scalar deleting destructor'(unsigned int)	C++
 	DeviceTest.exe!r8b::CPtrKeeper<r8b::CDSPBlockConvolver *>::~CPtrKeeper<r8b::CDSPBlockConvolver *>() Line 491	C++
 	DeviceTest.exe!`eh vector destructor iterator'(void * ptr, unsigned int size, int count, void (void *) * pDtor)	C++
 	DeviceTest.exe!r8b::CDSPResampler<r8b::CDSPFracInterpolator<24,673,9> >::~CDSPResampler<r8b::CDSPFracInterpolator<24,673,9> >()	C++
 	DeviceTest.exe!r8b::CDSPResampler24::~CDSPResampler24()	C++
 	DeviceTest.exe!r8b::CDSPResampler24::`scalar deleting destructor'(unsigned int)	C++
 	DeviceTest.exe!std::default_delete<r8b::CDSPResampler24>::operator()(r8b::CDSPResampler24 * _Ptr) Line 1152	C++
 	DeviceTest.exe!std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >::_Delete() Line 1445	C++
 	DeviceTest.exe!std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >::~unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >() Line 1400	C++
 	DeviceTest.exe!std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >::`scalar deleting destructor'(unsigned int)	C++
 	DeviceTest.exe!std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > >::destroy<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > >(std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Ptr) Line 624	C++
 	DeviceTest.exe!std::allocator_traits<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >::destroy<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > >(std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > & _Al, std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Ptr) Line 758	C++
 	DeviceTest.exe!std::_Wrap_alloc<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >::destroy<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > >(std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Ptr) Line 909	C++
 	DeviceTest.exe!std::_Destroy_range<std::_Wrap_alloc<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > > >(std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _First, std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Last, std::_Wrap_alloc<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > > & _Al, std::_Nonscalar_ptr_iterator_tag __formal) Line 89	C++
 	DeviceTest.exe!std::_Destroy_range<std::_Wrap_alloc<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > > >(std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _First, std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Last, std::_Wrap_alloc<std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > > & _Al) Line 80	C++
 	DeviceTest.exe!std::vector<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >,std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >::_Destroy(std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _First, std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > * _Last) Line 1480	C++
 	DeviceTest.exe!std::vector<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >,std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >::_Tidy() Line 1541	C++
 	DeviceTest.exe!std::vector<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >,std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >::~vector<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> >,std::allocator<std::unique_ptr<r8b::CDSPResampler24,std::default_delete<r8b::CDSPResampler24> > > >() Line 901	C++
 	DeviceTest.exe!cinder::audio2::dsp::ConverterImplR8brain::~ConverterImplR8brain() Line 66	C++

Post

Ah, I think I found the reason: the node that is using CDSPResampler is ultimately maintained by a static 'master' context, that is deallocated at app shutdown. This must be happening after the static objects used by r8brain are destroyed in atexit, leading to the bad access.

I can acommodate for this in my design, although it does bring up a question that has come to mind before, which is why there are static objects used by the converters. Are you saving memory by reusing buffers / processors, or is it a performance optimization?

Thanks again,
Rich

Post

r8brain looks like a great library, but I can't get a (very simple) example to work.
Here is my code:

const double input_sample_rate = 48000.0 ;
const double output_sample_rate = 44100.0 ;
const int input_sample_count = 2880 ;

// double in [input_sample_count] ;
// A passed-in single channel sample of a 440Hz sine wave. Approx 50 millisecs in duration.
// Values in the buffer are between -1.0 and 1.0

CDSPResampler24 resampler ( input_sample_rate, output_sample_rate, input_sample_count ) ;
double * out ;
int output_sample_count = resampler.process( in, input_sample_count, out );

output_sample_count is returned as 1346 which looks wrong to me. I'd expect it to be approximately twice that. Also the output of successive converted samples played through a speaker sounds like the right frequency but very jittery, not smooth.

I am instantiating a new resampler local object to handle each small sample. Is that what I am doing wrong? Should I use a single, persistent object on which I call process() for each sample?

Environment: Visual Studio 2012 32-bit C++ native code build

Post

Yeah, you will probably need a persistent resampler object; that is what I do anyway. If you use the resampler to resample unrelated sample bocks (e.g. different IRs), then you will need to clear the resampler object before doing a new block.

Post

Please follow the example code in the "example.cpp". Basically, you can't resample audio with r8brain-free-src in "one shot", it should be done iteratively.

Using a single persistent resampler object is a more efficient way to use this library (given the input and output sample rates remain same). Just call the "clear()" function before each resampling process.
Image

Post

reakinator wrote:I can acommodate for this in my design, although it does bring up a question that has come to mind before, which is why there are static objects used by the converters. Are you saving memory by reusing buffers / processors, or is it a performance optimization?
This library uses static objects to collect reusable filter objects.

I may rethink this approach. However, it will require to maintain and pass to the resampler a special "cache" object, which is a bit more complicated approach than using static objects.
Image

Post

I am using a single persistent object now and the resampling is working fine. An excellent library.

Post

I have recently updated to r69, and the Xcode LLVM compiler complained about two calls to implicitly-deleted default constructor. But I just found that this has been fixed in r70. Thanks! :)
Aleksey Vaneev wrote:Using a single persistent resampler object is a more efficient way to use this library (given the input and output sample rates remain same).
Not necessarily, I have found that sometimes it is more efficient to create a local (non-persistent) resampler object each call.

Post

Oops, I forgot to mention the warning I have been getting in VS2013:

Code: Select all

CDSPResampler.h(375) : warning C4373: 'r8b::CDSPResampler<r8b::CDSPFracInterpolator<18,137>>::process': virtual function overrides 'r8b::CDSPProcessor::process', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers
CDSPProcessor.h(107) : see declaration of 'r8b::CDSPProcessor::process'
CDSPResampler.h(496) : see reference to class template instantiation 'r8b::CDSPResampler<r8b::CDSPFracInterpolator<18,137>>' being compiled
Would it be safe to ignore this warning? Note that everything seems to work just fine.

EDIT: I have found what causes the warning, it is this line (#375) in CDSPResampler.h:

Code: Select all

virtual int process( double* const ip0, int l, double*& op0 )
If I remove the "const" the warning goes away, i.e.:

Code: Select all

virtual int process( double* ip0, int l, double*& op0 )

Post

Thanks for this library, I am using it in my mobile piano app to handle resampling audio sources that are not the same as my internal sample rate. Performance is quite good, although I am on ARM.

I have a few questions:
- On Android I use short * PCM data. What is recommended in this case for r8brain input: -1.0 <-> 1.0 or (double)SHRT_MIN <-> (double)SHRT_MAX? Both seem to work ok, but I don't know what the input limits are?
- Second is a issue: I get overflowed data back from r8brain. Is this expected? For example, when my audio data is between -1.0 and 1.0 range, I sometimes get values back that are > 1.0, like 1.00123. It's not hard to manually check all output, but I did not expect it to happen. (22050 => 44100)
- I am not an expert at all in resampling and all audio processing stuff. So I was wondering, what is the difference between a CDSPResampler16 and CDSPResampler24? Has the CDSPResampler24 any benefits when using 16 bit pcm data? I assume it's slower?

Thanks again for this library!

Post

I can't answer the other questions, but:
Peterdk wrote: - Second is a issue: I get overflowed data back from r8brain. Is this expected? For example, when my audio data is between -1.0 and 1.0 range, I sometimes get values back that are > 1.0, like 1.00123. It's not hard to manually check all output, but I did not expect it to happen. (22050 => 44100)
This is not just expected, it's also the desired result from any half-decent resampling library. The digital audio signal does not represent a series of steps or points, but a series of scaled and time-shifted cardinal sines (sinc functions). The actual audio signal is the sum of these functions (which are all infinite in time as well .. in theory anyway) and in general the maximum/minimum values for the signal are larger than the maximum/minimum values for the individual samples.

When you resample a signal, you are conceptually reconstructing the actual continuous audio signal, then filtering it (if necessary), and then sampling the result. When up-sampling, some of the samples are taken between the original samples and can be larger than the input samples (because the signal is larger there). When down-sampling, the filtering required to band-limit the signal to the lower bandwidth can also add some further ringing.

The upper limit is something like 3dB higher than the input sample maximum, though in practice you need a specially crafted worst-case signal to observe this much overshoot. All this is perfectly normal though and it also happens with analog signals when they finally come out of your computer (and in some cases, you can actually distort badly designed amplifiers as a result). In practice you might (or might not) want to leave some headroom and then just clip it and accept the distortion if it's not enough.

From the scientific point of view, the whole idea of trying to push digital signals to "full scale" is completely ridiculous, but good luck trying to explain that to users. :)

Post

Peterdk wrote:- Second is a issue: I get overflowed data back from r8brain. Is this expected? For example, when my audio data is between -1.0 and 1.0 range, I sometimes get values back that are > 1.0, like 1.00123. It's not hard to manually check all output, but I did not expect it to happen. (22050 => 44100)
Besides mystrans excellent answer, this effect also gave rise to "True-peak" meters/limiters etc., that process an oversampled (4x usually, and thus closer to the continous analog signal) version. The oversampled processing means that intersample-peaks can be avoided. What is important to realize is, that your original signal already contained these peaks, and the resampling shifted your waveform to some extent, so it actually is measureable in the discrete domain.

Post

Tale, thanks for the warning - I'll make a fix in a future update.

Peterdk, the library can handle any signal scale. As others have replied already, resampling does produce signal overshoots due to filtering.

CDSPResampler24 is slower than CDSPResampler16, but it performs a full-resolution 24-bit resampling. For "consumer" audio signal resampling (e.g. music playback) CDSPResampler16 should be enough.
Image

Post

Woah this looks great! Nice work man! Quick question though! I assume it has not been tested in Xcode or it would have been mentioned? Any problems I should be aware of when using with Xcode ?

thanks!

Post Reply

Return to “DSP and Plugin Development”