To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / CollidoscopeApp / src / BufferToWaveRecorderNode.cpp @ 3:7fb593d53361
History | View | Annotate | Download (8.6 KB)
| 1 |
#include "BufferToWaveRecorderNode.h" |
|---|---|
| 2 |
#include "cinder/audio/Context.h" |
| 3 |
#include "cinder/audio/Target.h" |
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
// ----------------------------------------------------------------------------------------------------
|
| 8 |
// MARK: - BufferRecorderNode
|
| 9 |
// ----------------------------------------------------------------------------------------------------
|
| 10 |
|
| 11 |
namespace {
|
| 12 |
|
| 13 |
const size_t DEFAULT_RECORD_BUFFER_FRAMES = 44100; |
| 14 |
|
| 15 |
void resizeBufferAndShuffleChannels(ci::audio::BufferDynamic *buffer, size_t resultNumFrames)
|
| 16 |
{
|
| 17 |
const size_t currentNumFrames = buffer->getNumFrames();
|
| 18 |
const size_t sampleSize = sizeof(ci::audio::BufferDynamic::SampleType); |
| 19 |
|
| 20 |
if (currentNumFrames < resultNumFrames) {
|
| 21 |
// if expanding, resize and then shuffle. Make sure to get the data pointer after the resize.
|
| 22 |
buffer->setNumFrames(resultNumFrames); |
| 23 |
float *data = buffer->getData();
|
| 24 |
|
| 25 |
for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) { |
| 26 |
const size_t numZeroFrames = resultNumFrames - currentNumFrames;
|
| 27 |
const float *currentChannel = &data[ch * currentNumFrames]; |
| 28 |
float *resultChannel = &data[ch * resultNumFrames];
|
| 29 |
|
| 30 |
memmove(resultChannel, currentChannel, currentNumFrames * sampleSize); |
| 31 |
memset(resultChannel - numZeroFrames, 0, numZeroFrames * sampleSize);
|
| 32 |
} |
| 33 |
} |
| 34 |
else if (currentNumFrames > resultNumFrames) { |
| 35 |
// if shrinking, shuffle first and then resize.
|
| 36 |
float *data = buffer->getData();
|
| 37 |
|
| 38 |
for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) { |
| 39 |
const float *currentChannel = &data[ch * currentNumFrames]; |
| 40 |
float *resultChannel = &data[ch * resultNumFrames];
|
| 41 |
|
| 42 |
memmove(resultChannel, currentChannel, currentNumFrames * sampleSize); |
| 43 |
} |
| 44 |
|
| 45 |
const size_t numZeroFrames = (currentNumFrames - resultNumFrames) * buffer->getNumChannels();
|
| 46 |
memset(data + buffer->getSize() - numZeroFrames, 0, numZeroFrames * sampleSize);
|
| 47 |
|
| 48 |
buffer->setNumFrames(resultNumFrames); |
| 49 |
} |
| 50 |
} |
| 51 |
|
| 52 |
} |
| 53 |
|
| 54 |
|
| 55 |
BufferToWaveRecorderNode::BufferToWaveRecorderNode( std::size_t numChunks, double numSeconds )
|
| 56 |
: SampleRecorderNode( Format().channels( 1 ) ),
|
| 57 |
mLastOverrun( 0 ),
|
| 58 |
mNumChunks( numChunks ), |
| 59 |
mNumSeconds( numSeconds ), |
| 60 |
mRingBuffer( numChunks ), |
| 61 |
mChunkMaxAudioVal( kMinAudioVal ), |
| 62 |
mChunkMinAudioVal( kMaxAudioVal ), |
| 63 |
mChunkSampleCounter( 0 ),
|
| 64 |
mChunkIndex( 0 )
|
| 65 |
{
|
| 66 |
|
| 67 |
} |
| 68 |
|
| 69 |
void BufferToWaveRecorderNode::initialize()
|
| 70 |
{
|
| 71 |
// adjust recorder buffer to match channels once initialized, since they could have changed since construction.
|
| 72 |
bool resize = mRecorderBuffer.getNumFrames() != 0; |
| 73 |
mRecorderBuffer.setNumChannels( getNumChannels() ); |
| 74 |
|
| 75 |
// lenght of buffer is = number of seconds * sample rate
|
| 76 |
initBuffers( size_t( mNumSeconds * (double)getSampleRate() ) );
|
| 77 |
|
| 78 |
// How many samples each chunk contains. That is it calculates the min and max of
|
| 79 |
// This is calculated here and not in the initializer list because it uses getNumFrames()
|
| 80 |
// FIXME probably could be done in constructor body
|
| 81 |
mNumSamplesPerChunk = std::lround( float( getNumFrames() ) / mNumChunks );
|
| 82 |
|
| 83 |
// if the buffer had already been resized, zero out any possibly existing data.
|
| 84 |
if( resize )
|
| 85 |
mRecorderBuffer.zero(); |
| 86 |
|
| 87 |
mEnvRampLen = kRampTime * getSampleRate(); |
| 88 |
mEnvDecayStart = mRecorderBuffer.getNumFrames() - mEnvRampLen; |
| 89 |
if ( mEnvRampLen <= 0 ){ |
| 90 |
mEnvRampRate = 0;
|
| 91 |
} |
| 92 |
else{
|
| 93 |
mEnvRampRate = 1.0f / mEnvRampLen; |
| 94 |
} |
| 95 |
} |
| 96 |
|
| 97 |
void BufferToWaveRecorderNode::initBuffers(size_t numFrames)
|
| 98 |
{
|
| 99 |
mRecorderBuffer.setSize( numFrames, getNumChannels() ); |
| 100 |
mCopiedBuffer = std::make_shared<ci::audio::BufferDynamic>( numFrames, getNumChannels() ); |
| 101 |
} |
| 102 |
|
| 103 |
void BufferToWaveRecorderNode::start()
|
| 104 |
{
|
| 105 |
mWritePos = 0;
|
| 106 |
mChunkIndex = 0;
|
| 107 |
enable(); |
| 108 |
} |
| 109 |
|
| 110 |
void BufferToWaveRecorderNode::stop()
|
| 111 |
{
|
| 112 |
disable(); |
| 113 |
} |
| 114 |
|
| 115 |
void BufferToWaveRecorderNode::setNumSeconds(double numSeconds, bool shrinkToFit) |
| 116 |
{
|
| 117 |
setNumFrames(size_t(numSeconds * (double)getSampleRate()), shrinkToFit);
|
| 118 |
} |
| 119 |
|
| 120 |
double BufferToWaveRecorderNode::getNumSeconds() const |
| 121 |
{
|
| 122 |
return (double)getNumFrames() / (double)getSampleRate(); |
| 123 |
} |
| 124 |
|
| 125 |
void BufferToWaveRecorderNode::setNumFrames(size_t numFrames, bool shrinkToFit) |
| 126 |
{
|
| 127 |
if (mRecorderBuffer.getNumFrames() == numFrames)
|
| 128 |
return;
|
| 129 |
|
| 130 |
std::lock_guard<std::mutex> lock(getContext()->getMutex()); |
| 131 |
|
| 132 |
if (mWritePos != 0) |
| 133 |
resizeBufferAndShuffleChannels(&mRecorderBuffer, numFrames); |
| 134 |
else
|
| 135 |
mRecorderBuffer.setNumFrames(numFrames); |
| 136 |
|
| 137 |
if (shrinkToFit)
|
| 138 |
mRecorderBuffer.shrinkToFit(); |
| 139 |
} |
| 140 |
|
| 141 |
ci::audio::BufferRef BufferToWaveRecorderNode::getRecordedCopy() const
|
| 142 |
{
|
| 143 |
// first grab the number of current frames, which may be increasing as the recording continues.
|
| 144 |
size_t numFrames = mWritePos; |
| 145 |
mCopiedBuffer->setSize(numFrames, mRecorderBuffer.getNumChannels()); |
| 146 |
|
| 147 |
mCopiedBuffer->copy(mRecorderBuffer, numFrames); |
| 148 |
return mCopiedBuffer;
|
| 149 |
} |
| 150 |
|
| 151 |
void BufferToWaveRecorderNode::writeToFile(const ci::fs::path &filePath, ci::audio::SampleType sampleType) |
| 152 |
{
|
| 153 |
size_t currentWritePos = mWritePos; |
| 154 |
ci::audio::BufferRef copiedBuffer = getRecordedCopy(); |
| 155 |
|
| 156 |
ci::audio::TargetFileRef target = ci::audio::TargetFile::create(filePath, getSampleRate(), getNumChannels(), sampleType); |
| 157 |
target->write(copiedBuffer.get(), currentWritePos); |
| 158 |
} |
| 159 |
|
| 160 |
uint64_t BufferToWaveRecorderNode::getLastOverrun() |
| 161 |
{
|
| 162 |
uint64_t result = mLastOverrun; |
| 163 |
mLastOverrun = 0;
|
| 164 |
return result;
|
| 165 |
} |
| 166 |
|
| 167 |
|
| 168 |
void BufferToWaveRecorderNode::process(ci::audio::Buffer *buffer)
|
| 169 |
{
|
| 170 |
size_t writePos = mWritePos; |
| 171 |
size_t numWriteFrames = buffer->getNumFrames(); |
| 172 |
|
| 173 |
if ( writePos == 0 ){ |
| 174 |
RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_START, 0, 0, 0 ); |
| 175 |
mRingBuffer.write( &msg, 1 );
|
| 176 |
|
| 177 |
// reset everything
|
| 178 |
mChunkMinAudioVal = kMaxAudioVal; |
| 179 |
mChunkMaxAudioVal = kMinAudioVal; |
| 180 |
mChunkSampleCounter = 0;
|
| 181 |
mChunkIndex = 0;
|
| 182 |
mEnvRamp = 0.0f; |
| 183 |
} |
| 184 |
|
| 185 |
// if buffer has too many frames (because we're nearly at the end or at the end )
|
| 186 |
// of mRecoderBuffer then numWriteFrames becomes the number of samples left to
|
| 187 |
// fill mRecorderBuffer. Which is 0 if the buffer is at the end.
|
| 188 |
if ( writePos + numWriteFrames > mRecorderBuffer.getNumFrames() )
|
| 189 |
numWriteFrames = mRecorderBuffer.getNumFrames() - writePos; |
| 190 |
|
| 191 |
if ( numWriteFrames <= 0 ) |
| 192 |
return;
|
| 193 |
|
| 194 |
|
| 195 |
// apply envelope to the buffer at the edges to avoid clicks
|
| 196 |
if ( writePos < mEnvRampLen ){ // beginning of wave |
| 197 |
for ( size_t i = 0; i < std::min( mEnvRampLen, numWriteFrames ); i++ ){ |
| 198 |
buffer->getData()[i] *= mEnvRamp; |
| 199 |
mEnvRamp += mEnvRampRate; |
| 200 |
if ( mEnvRamp > 1.0f ) |
| 201 |
mEnvRamp = 1.0f; |
| 202 |
} |
| 203 |
} |
| 204 |
else if ( writePos + numWriteFrames > mEnvDecayStart ){ // end of wave |
| 205 |
for ( size_t i = std::max( writePos, mEnvDecayStart ) - writePos; i < numWriteFrames; i++ ){
|
| 206 |
buffer->getData()[i] *= mEnvRamp; |
| 207 |
mEnvRamp -= mEnvRampRate; |
| 208 |
if ( mEnvRamp < 0.0f ) |
| 209 |
mEnvRamp = 0.0f; |
| 210 |
} |
| 211 |
} |
| 212 |
|
| 213 |
|
| 214 |
mRecorderBuffer.copyOffset(*buffer, numWriteFrames, writePos, 0);
|
| 215 |
|
| 216 |
if ( numWriteFrames < buffer->getNumFrames() )
|
| 217 |
mLastOverrun = getContext()->getNumProcessedFrames(); |
| 218 |
|
| 219 |
/* find max and minimum of this buffer */
|
| 220 |
for ( size_t i = 0; i < numWriteFrames; i++ ){ |
| 221 |
|
| 222 |
if ( buffer->getData()[i] < mChunkMinAudioVal ){
|
| 223 |
mChunkMinAudioVal = buffer->getData()[i]; |
| 224 |
} |
| 225 |
|
| 226 |
if ( buffer->getData()[i] > mChunkMaxAudioVal ){
|
| 227 |
mChunkMaxAudioVal = buffer->getData()[i]; |
| 228 |
} |
| 229 |
|
| 230 |
if ( mChunkSampleCounter >= mNumSamplesPerChunk // if collected enough samples |
| 231 |
|| writePos + i >= mRecorderBuffer.getNumFrames() - 1 ){ // or at the end of recorder buffer |
| 232 |
// send chunk to GUI
|
| 233 |
size_t chunkIndex = mChunkIndex.fetch_add( 1 );
|
| 234 |
|
| 235 |
RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_CHUNK, chunkIndex, mChunkMinAudioVal, mChunkMaxAudioVal ); |
| 236 |
mRingBuffer.write( &msg, 1 );
|
| 237 |
|
| 238 |
// reset chunk info
|
| 239 |
mChunkMinAudioVal = kMaxAudioVal; |
| 240 |
mChunkMaxAudioVal = kMinAudioVal; |
| 241 |
mChunkSampleCounter = 0;
|
| 242 |
} |
| 243 |
else{
|
| 244 |
mChunkSampleCounter++; |
| 245 |
} |
| 246 |
} |
| 247 |
|
| 248 |
// check if write position has been reset by the GUI thread, if not write new value
|
| 249 |
const size_t writePosNew = writePos + numWriteFrames;
|
| 250 |
mWritePos.compare_exchange_strong( writePos, writePosNew ); |
| 251 |
|
| 252 |
} |
| 253 |
|
| 254 |
|
| 255 |
const float BufferToWaveRecorderNode::kMinAudioVal = -1.0f; |
| 256 |
const float BufferToWaveRecorderNode::kMaxAudioVal = 1.0f; |
| 257 |
const float BufferToWaveRecorderNode::kRampTime = 0.02; |
| 258 |
|
| 259 |
|