From 7d8a4395cd398615dd29c4ddd720f1520eb66adc Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Mon, 10 Jul 2017 00:34:50 +0200 Subject: [PATCH 1/6] Add sample rate conversion to TargetFile and BufferRecorderNode (Cocoa only for now) --- include/cinder/audio/SampleRecorderNode.h | 4 +- include/cinder/audio/Target.h | 26 +++++++--- include/cinder/audio/cocoa/FileCoreAudio.h | 2 +- src/cinder/audio/SampleRecorderNode.cpp | 4 +- src/cinder/audio/Target.cpp | 59 +++++++++++++++++++--- src/cinder/audio/cocoa/FileCoreAudio.cpp | 10 ++-- 6 files changed, 81 insertions(+), 24 deletions(-) diff --git a/include/cinder/audio/SampleRecorderNode.h b/include/cinder/audio/SampleRecorderNode.h index 02deb87f04..b6dd44fd11 100644 --- a/include/cinder/audio/SampleRecorderNode.h +++ b/include/cinder/audio/SampleRecorderNode.h @@ -75,9 +75,9 @@ class CI_API BufferRecorderNode : public SampleRecorderNode { //! \brief Writes the currently recorded samples to a file at \a filePath //! - //! The encoding format is derived from \a filePath's extension and \a sampleType (default = SampleType::INT_16). + //! The encoding format is derived from \a filePath's extension, \a sampleType (default = SampleType::INT_16) and \a destSampleRate (default = getSampleRate()). //! \note throws AudioFileExc if the write request cannot be completed. - void writeToFile( const ci::fs::path &filePath, SampleType sampleType = SampleType::INT_16 ); + void writeToFile( const ci::fs::path &filePath, SampleType sampleType = SampleType::INT_16, size_t destSampleRate = 0 ); //! Returns the frame of the last buffer overrun or 0 if none since the last time this method was called. When this happens, it means the recorded buffer probably has skipped some frames. uint64_t getLastOverrun(); diff --git a/include/cinder/audio/Target.h b/include/cinder/audio/Target.h index 990edbdf0d..bd2fbe3e8d 100644 --- a/include/cinder/audio/Target.h +++ b/include/cinder/audio/Target.h @@ -32,30 +32,42 @@ namespace cinder { namespace audio { typedef std::shared_ptr TargetFileRef; +namespace dsp { + class Converter; +} + //! Base class that is used to create and write to an audio destination. Currently only supports .wav encoding. class CI_API TargetFile { public: - static std::unique_ptr create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, const std::string &extension = "" ); - static std::unique_ptr create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, const std::string &extension = "" ); - virtual ~TargetFile() {} + static std::unique_ptr create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t targetSampleRate = 0, const std::string &extension = "" ); + static std::unique_ptr create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t targetSampleRate = 0, const std::string &extension = "" ); + virtual ~TargetFile(); void write( const Buffer *buffer ); void write( const Buffer *buffer, size_t numFrames ); void write( const Buffer *buffer, size_t numFrames, size_t frameOffset ); + //! Returns the user facing sample rate (input) size_t getSampleRate() const { return mSampleRate; } + //! Returns the true sample rate of the target file. \note Actual input samplerate may differ. \see getSampleRate() + size_t getSampleRateTarget() const { return mSampleRateTarget; } + size_t getNumChannels() const { return mNumChannels; } protected: - TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType ) - : mSampleRate( sampleRate ), mNumChannels( numChannels ), mSampleType( sampleType ) - {} + TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t destSampleRate = 0 ); // Implement to write \a numFrames frames of \a buffer to file. The writing begins at \a frameOffset. virtual void performWrite( const Buffer *buffer, size_t numFrames, size_t frameOffset ) = 0; - size_t mSampleRate, mNumChannels; + // Sets up samplerate conversion if needed. + void setupSampleRateConversion(); + + size_t mSampleRate, mSampleRateTarget, mNumChannels, mMaxFramesPerConversion; SampleType mSampleType; + + std::unique_ptr mConverter; + BufferDynamic mConverterSourceBuffer, mConverterDestBuffer; }; } } // namespace cinder::audio diff --git a/include/cinder/audio/cocoa/FileCoreAudio.h b/include/cinder/audio/cocoa/FileCoreAudio.h index 530917d73f..f8b51b66ea 100644 --- a/include/cinder/audio/cocoa/FileCoreAudio.h +++ b/include/cinder/audio/cocoa/FileCoreAudio.h @@ -69,7 +69,7 @@ class SourceFileCoreAudio : public SourceFile { class TargetFileCoreAudio : public TargetFile { public: - TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ); + TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ); virtual ~TargetFileCoreAudio() {} void performWrite( const Buffer *buffer, size_t numFrames, size_t frameOffset ) override; diff --git a/src/cinder/audio/SampleRecorderNode.cpp b/src/cinder/audio/SampleRecorderNode.cpp index e65d635ff7..d104bb9e79 100644 --- a/src/cinder/audio/SampleRecorderNode.cpp +++ b/src/cinder/audio/SampleRecorderNode.cpp @@ -162,12 +162,12 @@ BufferRef BufferRecorderNode::getRecordedCopy() const return mCopiedBuffer; } -void BufferRecorderNode::writeToFile( const fs::path &filePath, SampleType sampleType ) +void BufferRecorderNode::writeToFile( const fs::path &filePath, SampleType sampleType, size_t destSampleRate ) { size_t currentWritePos = mWritePos; BufferRef copiedBuffer = getRecordedCopy(); - audio::TargetFileRef target = audio::TargetFile::create( filePath, getSampleRate(), getNumChannels(), sampleType ); + audio::TargetFileRef target = audio::TargetFile::create( filePath, getSampleRate(), getNumChannels(), sampleType, destSampleRate ); target->write( copiedBuffer.get(), currentWritePos ); } diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index 7c490acec9..e70b32d718 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -22,6 +22,7 @@ */ #include "cinder/audio/Target.h" +#include "cinder/audio/dsp/Converter.h" #include "cinder/CinderAssert.h" #include "cinder/Utilities.h" @@ -38,7 +39,7 @@ namespace cinder { namespace audio { // TODO: these should be replaced with a generic registrar derived from the ImageIo stuff. -std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) +std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateTarget, const std::string &extension ) { #if ! defined( CINDER_UWP ) || ( _MSC_VER > 1800 ) std::string ext = dataTarget->getFilePathHint().extension().string(); @@ -48,20 +49,42 @@ std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, ext = ( ( ! ext.empty() ) && ( ext[0] == '.' ) ) ? ext.substr( 1, string::npos ) : ext; #if defined( CINDER_COCOA ) - return std::unique_ptr( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, ext ) ); + return std::unique_ptr( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, sampleRateTarget, ext ) ); #elif defined( CINDER_MSW ) + CI_ASSERT_MSG( sampleRateTarget == 0 || sampleRateTarget == sampleRate, "sample rate conversion not yet implemented on MSW" ); return std::unique_ptr( new msw::TargetFileMediaFoundation( dataTarget, sampleRate, numChannels, sampleType, ext ) ); #endif } -std::unique_ptr TargetFile::create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) +std::unique_ptr TargetFile::create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ) { - return create( (DataTargetRef)writeFile( path ), sampleRate, numChannels, sampleType, extension ); + return create( (DataTargetRef)writeFile( path ), sampleRate, numChannels, sampleType, targetSampleRate, extension ); +} + +TargetFile::TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateTarget ) + : mSampleRate( sampleRate ), mNumChannels( numChannels ), mSampleType( sampleType ), mSampleRateTarget( sampleRateTarget ), mMaxFramesPerConversion( 4092 ) +{ + setupSampleRateConversion(); +} + +TargetFile::~TargetFile() +{ +} + +void TargetFile::setupSampleRateConversion() +{ + if ( ! mSampleRateTarget ) { + mSampleRateTarget = mSampleRate; + } else if ( mSampleRateTarget != mSampleRate) { + mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateTarget, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); + mConverterSourceBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); + mConverterDestBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); + } } void TargetFile::write( const Buffer *buffer ) { - performWrite( buffer, buffer->getNumFrames(), 0 ); + write( buffer, buffer->getNumFrames(), 0 ); } void TargetFile::write( const Buffer *buffer, size_t numFrames ) @@ -71,7 +94,7 @@ void TargetFile::write( const Buffer *buffer, size_t numFrames ) CI_ASSERT_MSG( numFrames <= buffer->getNumFrames(), "numFrames out of bounds" ); - performWrite( buffer, numFrames, 0 ); + write( buffer, numFrames, 0 ); } void TargetFile::write( const Buffer *buffer, size_t numFrames, size_t frameOffset ) @@ -81,7 +104,29 @@ void TargetFile::write( const Buffer *buffer, size_t numFrames, size_t frameOffs CI_ASSERT_MSG( numFrames + frameOffset <= buffer->getNumFrames(), "numFrames + frameOffset out of bounds" ); - performWrite( buffer, numFrames, frameOffset ); + if( mConverter ) { + auto currFrame = frameOffset; + auto lastFrame = frameOffset + numFrames; + + // process buffer in chunks of mMaxFramesPerConversion + while ( currFrame != lastFrame ) { + auto numSourceFrames = std::min( mMaxFramesPerConversion, lastFrame - currFrame ); + auto numDestFrames = size_t( numSourceFrames * (float)getSampleRateTarget() / (float)getSampleRate() ); + + // copy buffer into temporary buffer to remove frame offset (needed for mConverter->convert) + mConverterSourceBuffer.copyOffset( *buffer, numSourceFrames, 0, currFrame ); + + mConverterSourceBuffer.setNumFrames( numSourceFrames ); + mConverterDestBuffer.setNumFrames( numDestFrames ); + tie( numSourceFrames, numDestFrames ) = mConverter->convert( &mConverterSourceBuffer, &mConverterDestBuffer ); + + performWrite( &mConverterDestBuffer, numDestFrames, 0 ); + + currFrame += numSourceFrames; + } + } else { + performWrite( buffer, numFrames, frameOffset ); + } } } } // namespace cinder::audio diff --git a/src/cinder/audio/cocoa/FileCoreAudio.cpp b/src/cinder/audio/cocoa/FileCoreAudio.cpp index 3120405bec..328ed496b1 100644 --- a/src/cinder/audio/cocoa/FileCoreAudio.cpp +++ b/src/cinder/audio/cocoa/FileCoreAudio.cpp @@ -147,8 +147,8 @@ vector SourceFileCoreAudio::getSupportedExtensions() // MARK: - TargetFileCoreAudio // ---------------------------------------------------------------------------------------------------- -TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) - : TargetFile( sampleRate, numChannels, sampleType ) +TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ) + : TargetFile( sampleRate, numChannels, sampleType, targetSampleRate ) { ::CFURLRef targetUrl = ci::cocoa::createCfUrl( Url( dataTarget->getFilePath().string() ) ); ::AudioFileTypeID fileType = getFileTypeIdFromExtension( extension ); @@ -156,10 +156,10 @@ TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_ ::AudioStreamBasicDescription fileAsbd; switch( mSampleType ) { case SampleType::INT_16: - fileAsbd = createInt16Asbd( mSampleRate, mNumChannels, true ); + fileAsbd = createInt16Asbd( mSampleRateTarget, mNumChannels, true ); break; case SampleType::FLOAT_32: - fileAsbd = createFloatAsbd( mSampleRate, mNumChannels, true ); + fileAsbd = createFloatAsbd( mSampleRateTarget, mNumChannels, true ); break; default: CI_ASSERT_NOT_REACHABLE(); @@ -175,7 +175,7 @@ TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_ ::CFRelease( targetUrl ); mExtAudioFile = ExtAudioFilePtr( audioFile ); - ::AudioStreamBasicDescription clientAsbd = createFloatAsbd( mSampleRate, mNumChannels, false ); + ::AudioStreamBasicDescription clientAsbd = createFloatAsbd( mSampleRateTarget, mNumChannels, false ); status = ::ExtAudioFileSetProperty( mExtAudioFile.get(), kExtAudioFileProperty_ClientDataFormat, sizeof( clientAsbd ), &clientAsbd ); CI_VERIFY( status == noErr ); From af4b0e6be1119e76fed1280c8cf16b5f299c906d Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Tue, 18 Jul 2017 12:18:56 +0200 Subject: [PATCH 2/6] Make default destructor TargetFile more explicit --- src/cinder/audio/Target.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index e70b32d718..5fcb350888 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -67,9 +67,7 @@ TargetFile::TargetFile( size_t sampleRate, size_t numChannels, SampleType sample setupSampleRateConversion(); } -TargetFile::~TargetFile() -{ -} +TargetFile::~TargetFile() = default; void TargetFile::setupSampleRateConversion() { From 89ad51f970dcde5932114aa5c9dbed3595bb9e2b Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Tue, 8 Aug 2017 23:09:40 +0200 Subject: [PATCH 3/6] Adjust to style guide --- src/cinder/audio/Target.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index 5fcb350888..443d2a6080 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -71,9 +71,10 @@ TargetFile::~TargetFile() = default; void TargetFile::setupSampleRateConversion() { - if ( ! mSampleRateTarget ) { + if( ! mSampleRateTarget ) { mSampleRateTarget = mSampleRate; - } else if ( mSampleRateTarget != mSampleRate) { + } + else if( mSampleRateTarget != mSampleRate) { mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateTarget, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); mConverterSourceBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); mConverterDestBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); @@ -107,7 +108,7 @@ void TargetFile::write( const Buffer *buffer, size_t numFrames, size_t frameOffs auto lastFrame = frameOffset + numFrames; // process buffer in chunks of mMaxFramesPerConversion - while ( currFrame != lastFrame ) { + while( currFrame != lastFrame ) { auto numSourceFrames = std::min( mMaxFramesPerConversion, lastFrame - currFrame ); auto numDestFrames = size_t( numSourceFrames * (float)getSampleRateTarget() / (float)getSampleRate() ); @@ -122,7 +123,8 @@ void TargetFile::write( const Buffer *buffer, size_t numFrames, size_t frameOffs currFrame += numSourceFrames; } - } else { + } + else { performWrite( buffer, numFrames, frameOffset ); } } From 8202cda14387fb5823940353c9d90f33c8b61003 Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Thu, 17 Aug 2017 00:15:49 +0200 Subject: [PATCH 4/6] Target: change getSampleRateTarget() to getSampleRateNative() --- include/cinder/audio/Target.h | 10 +++++----- include/cinder/audio/cocoa/FileCoreAudio.h | 2 +- src/cinder/audio/Target.cpp | 22 +++++++++++----------- src/cinder/audio/cocoa/FileCoreAudio.cpp | 10 +++++----- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/include/cinder/audio/Target.h b/include/cinder/audio/Target.h index bd2fbe3e8d..eed4f35350 100644 --- a/include/cinder/audio/Target.h +++ b/include/cinder/audio/Target.h @@ -39,8 +39,8 @@ namespace dsp { //! Base class that is used to create and write to an audio destination. Currently only supports .wav encoding. class CI_API TargetFile { public: - static std::unique_ptr create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t targetSampleRate = 0, const std::string &extension = "" ); - static std::unique_ptr create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t targetSampleRate = 0, const std::string &extension = "" ); + static std::unique_ptr create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t sampleRateNative = 0, const std::string &extension = "" ); + static std::unique_ptr create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType = SampleType::INT_16, size_t sampleRateNative = 0, const std::string &extension = "" ); virtual ~TargetFile(); void write( const Buffer *buffer ); @@ -50,12 +50,12 @@ class CI_API TargetFile { //! Returns the user facing sample rate (input) size_t getSampleRate() const { return mSampleRate; } //! Returns the true sample rate of the target file. \note Actual input samplerate may differ. \see getSampleRate() - size_t getSampleRateTarget() const { return mSampleRateTarget; } + size_t getSampleRateNative() const { return mSampleRateNative; } size_t getNumChannels() const { return mNumChannels; } protected: - TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t destSampleRate = 0 ); + TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative = 0 ); // Implement to write \a numFrames frames of \a buffer to file. The writing begins at \a frameOffset. virtual void performWrite( const Buffer *buffer, size_t numFrames, size_t frameOffset ) = 0; @@ -63,7 +63,7 @@ class CI_API TargetFile { // Sets up samplerate conversion if needed. void setupSampleRateConversion(); - size_t mSampleRate, mSampleRateTarget, mNumChannels, mMaxFramesPerConversion; + size_t mSampleRate, mSampleRateNative, mNumChannels, mMaxFramesPerConversion; SampleType mSampleType; std::unique_ptr mConverter; diff --git a/include/cinder/audio/cocoa/FileCoreAudio.h b/include/cinder/audio/cocoa/FileCoreAudio.h index f8b51b66ea..2e88fe7de9 100644 --- a/include/cinder/audio/cocoa/FileCoreAudio.h +++ b/include/cinder/audio/cocoa/FileCoreAudio.h @@ -69,7 +69,7 @@ class SourceFileCoreAudio : public SourceFile { class TargetFileCoreAudio : public TargetFile { public: - TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ); + TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative, const std::string &extension ); virtual ~TargetFileCoreAudio() {} void performWrite( const Buffer *buffer, size_t numFrames, size_t frameOffset ) override; diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index 443d2a6080..33ef8bdf69 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -39,7 +39,7 @@ namespace cinder { namespace audio { // TODO: these should be replaced with a generic registrar derived from the ImageIo stuff. -std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateTarget, const std::string &extension ) +std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative, const std::string &extension ) { #if ! defined( CINDER_UWP ) || ( _MSC_VER > 1800 ) std::string ext = dataTarget->getFilePathHint().extension().string(); @@ -49,20 +49,20 @@ std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, ext = ( ( ! ext.empty() ) && ( ext[0] == '.' ) ) ? ext.substr( 1, string::npos ) : ext; #if defined( CINDER_COCOA ) - return std::unique_ptr( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, sampleRateTarget, ext ) ); + return std::unique_ptr( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, sampleRateNative, ext ) ); #elif defined( CINDER_MSW ) CI_ASSERT_MSG( sampleRateTarget == 0 || sampleRateTarget == sampleRate, "sample rate conversion not yet implemented on MSW" ); return std::unique_ptr( new msw::TargetFileMediaFoundation( dataTarget, sampleRate, numChannels, sampleType, ext ) ); #endif } -std::unique_ptr TargetFile::create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ) +std::unique_ptr TargetFile::create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative, const std::string &extension ) { - return create( (DataTargetRef)writeFile( path ), sampleRate, numChannels, sampleType, targetSampleRate, extension ); + return create( (DataTargetRef)writeFile( path ), sampleRate, numChannels, sampleType, sampleRateNative, extension ); } -TargetFile::TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateTarget ) - : mSampleRate( sampleRate ), mNumChannels( numChannels ), mSampleType( sampleType ), mSampleRateTarget( sampleRateTarget ), mMaxFramesPerConversion( 4092 ) +TargetFile::TargetFile( size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative ) + : mSampleRate( sampleRate ), mNumChannels( numChannels ), mSampleType( sampleType ), mSampleRateNative( sampleRateNative ), mMaxFramesPerConversion( 4092 ) { setupSampleRateConversion(); } @@ -71,11 +71,11 @@ TargetFile::~TargetFile() = default; void TargetFile::setupSampleRateConversion() { - if( ! mSampleRateTarget ) { - mSampleRateTarget = mSampleRate; + if( ! mSampleRateNative ) { + mSampleRateNative = mSampleRate; } - else if( mSampleRateTarget != mSampleRate) { - mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateTarget, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); + else if( mSampleRateNative != mSampleRate) { + mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateNative, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); mConverterSourceBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); mConverterDestBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); } @@ -110,7 +110,7 @@ void TargetFile::write( const Buffer *buffer, size_t numFrames, size_t frameOffs // process buffer in chunks of mMaxFramesPerConversion while( currFrame != lastFrame ) { auto numSourceFrames = std::min( mMaxFramesPerConversion, lastFrame - currFrame ); - auto numDestFrames = size_t( numSourceFrames * (float)getSampleRateTarget() / (float)getSampleRate() ); + auto numDestFrames = size_t( numSourceFrames * (float)getSampleRateNative() / (float)getSampleRate() ); // copy buffer into temporary buffer to remove frame offset (needed for mConverter->convert) mConverterSourceBuffer.copyOffset( *buffer, numSourceFrames, 0, currFrame ); diff --git a/src/cinder/audio/cocoa/FileCoreAudio.cpp b/src/cinder/audio/cocoa/FileCoreAudio.cpp index 328ed496b1..754433378e 100644 --- a/src/cinder/audio/cocoa/FileCoreAudio.cpp +++ b/src/cinder/audio/cocoa/FileCoreAudio.cpp @@ -147,8 +147,8 @@ vector SourceFileCoreAudio::getSupportedExtensions() // MARK: - TargetFileCoreAudio // ---------------------------------------------------------------------------------------------------- -TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t targetSampleRate, const std::string &extension ) - : TargetFile( sampleRate, numChannels, sampleType, targetSampleRate ) +TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, size_t sampleRateNative, const std::string &extension ) + : TargetFile( sampleRate, numChannels, sampleType, sampleRateNative ) { ::CFURLRef targetUrl = ci::cocoa::createCfUrl( Url( dataTarget->getFilePath().string() ) ); ::AudioFileTypeID fileType = getFileTypeIdFromExtension( extension ); @@ -156,10 +156,10 @@ TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_ ::AudioStreamBasicDescription fileAsbd; switch( mSampleType ) { case SampleType::INT_16: - fileAsbd = createInt16Asbd( mSampleRateTarget, mNumChannels, true ); + fileAsbd = createInt16Asbd( getSampleRateNative(), mNumChannels, true ); break; case SampleType::FLOAT_32: - fileAsbd = createFloatAsbd( mSampleRateTarget, mNumChannels, true ); + fileAsbd = createFloatAsbd( getSampleRateNative(), mNumChannels, true ); break; default: CI_ASSERT_NOT_REACHABLE(); @@ -175,7 +175,7 @@ TargetFileCoreAudio::TargetFileCoreAudio( const DataTargetRef &dataTarget, size_ ::CFRelease( targetUrl ); mExtAudioFile = ExtAudioFilePtr( audioFile ); - ::AudioStreamBasicDescription clientAsbd = createFloatAsbd( mSampleRateTarget, mNumChannels, false ); + ::AudioStreamBasicDescription clientAsbd = createFloatAsbd( getSampleRateNative(), mNumChannels, false ); status = ::ExtAudioFileSetProperty( mExtAudioFile.get(), kExtAudioFileProperty_ClientDataFormat, sizeof( clientAsbd ), &clientAsbd ); CI_VERIFY( status == noErr ); From 1d1bba62fb72714712372a0844ec1b356bc769f8 Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Thu, 17 Aug 2017 00:28:05 +0200 Subject: [PATCH 5/6] Target: add supportsConversion() --- include/cinder/audio/Target.h | 2 ++ src/cinder/audio/Target.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/cinder/audio/Target.h b/include/cinder/audio/Target.h index eed4f35350..d6af1900f4 100644 --- a/include/cinder/audio/Target.h +++ b/include/cinder/audio/Target.h @@ -59,6 +59,8 @@ class CI_API TargetFile { // Implement to write \a numFrames frames of \a buffer to file. The writing begins at \a frameOffset. virtual void performWrite( const Buffer *buffer, size_t numFrames, size_t frameOffset ) = 0; + //! Implementations should override and return true if they can provide samplerate conversion. If false (default), a Converter will be used if needed. + virtual bool supportsConversion() { return false; } // Sets up samplerate conversion if needed. void setupSampleRateConversion(); diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index 33ef8bdf69..fc02b32d6d 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -75,9 +75,11 @@ void TargetFile::setupSampleRateConversion() mSampleRateNative = mSampleRate; } else if( mSampleRateNative != mSampleRate) { - mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateNative, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); - mConverterSourceBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); - mConverterDestBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); + if( ! supportsConversion() ) { + mConverter = audio::dsp::Converter::create( mSampleRate, mSampleRateNative, getNumChannels(), getNumChannels(), mMaxFramesPerConversion ); + mConverterSourceBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); + mConverterDestBuffer.setSize( mMaxFramesPerConversion, getNumChannels() ); + } } } From a9ea7ed6556bdc2c20c390c32792fa5c75c24b7f Mon Sep 17 00:00:00 2001 From: Koenraad Verheyden Date: Thu, 17 Aug 2017 00:52:52 +0200 Subject: [PATCH 6/6] Target: forgot to change sampleRateNative on MSW --- src/cinder/audio/Target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cinder/audio/Target.cpp b/src/cinder/audio/Target.cpp index fc02b32d6d..234097050f 100644 --- a/src/cinder/audio/Target.cpp +++ b/src/cinder/audio/Target.cpp @@ -51,7 +51,7 @@ std::unique_ptr TargetFile::create( const DataTargetRef &dataTarget, #if defined( CINDER_COCOA ) return std::unique_ptr( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, sampleRateNative, ext ) ); #elif defined( CINDER_MSW ) - CI_ASSERT_MSG( sampleRateTarget == 0 || sampleRateTarget == sampleRate, "sample rate conversion not yet implemented on MSW" ); + CI_ASSERT_MSG( sampleRateNative == 0 || sampleRateNative == sampleRate, "sample rate conversion not yet implemented on MSW" ); return std::unique_ptr( new msw::TargetFileMediaFoundation( dataTarget, sampleRate, numChannels, sampleType, ext ) ); #endif }