-
Notifications
You must be signed in to change notification settings - Fork 960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional sample rate conversion to audio::TargetFile and audio::BufferRecorderNode #1869
base: master
Are you sure you want to change the base?
Changes from 2 commits
7d8a439
af4b0e6
89ad51f
8202cda
1d1bba6
a9ea7ed
8f0036e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> TargetFile::create( const DataTargetRef &dataTarget, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) | ||
std::unique_ptr<TargetFile> 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,40 @@ std::unique_ptr<TargetFile> TargetFile::create( const DataTargetRef &dataTarget, | |
ext = ( ( ! ext.empty() ) && ( ext[0] == '.' ) ) ? ext.substr( 1, string::npos ) : ext; | ||
|
||
#if defined( CINDER_COCOA ) | ||
return std::unique_ptr<TargetFile>( new cocoa::TargetFileCoreAudio( dataTarget, sampleRate, numChannels, sampleType, ext ) ); | ||
return std::unique_ptr<TargetFile>( 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<TargetFile>( new msw::TargetFileMediaFoundation( dataTarget, sampleRate, numChannels, sampleType, ext ) ); | ||
#endif | ||
} | ||
|
||
std::unique_ptr<TargetFile> TargetFile::create( const fs::path &path, size_t sampleRate, size_t numChannels, SampleType sampleType, const std::string &extension ) | ||
std::unique_ptr<TargetFile> 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() = default; | ||
|
||
void TargetFile::setupSampleRateConversion() | ||
{ | ||
if ( ! mSampleRateTarget ) { | ||
mSampleRateTarget = mSampleRate; | ||
} else if ( mSampleRateTarget != mSampleRate) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just fixed this with 89ad51f. |
||
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 +92,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 +102,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 ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Concerning this and your comment in the PR description about this being a bit hacky, I hope that in the future this problem can be alleviated once |
||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's worth naming this
getSampleRateNative()
to be consistent withSourceFile
? I can't say I like one name over the other, though it is slightly ambiguous that the class is calledTargetFile
and the method has the noun 'target' in in.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me both seem reasonable. I personally didn't find
getSampleRateTarget()
ambiguous since I thought of it as "get the sample rate of the target [file]". But I can definitely see the consistency argument withSourceFile
.