annotate CollidoscopeApp/src/BufferToWaveRecorderNode.cpp @ 0:02467299402e

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