diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/BufferToWaveRecorderNode.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,259 @@
+#include "BufferToWaveRecorderNode.h"
+#include "cinder/audio/Context.h"
+#include "cinder/audio/Target.h"
+
+
+
+// ----------------------------------------------------------------------------------------------------
+// MARK: - BufferRecorderNode
+// ----------------------------------------------------------------------------------------------------
+
+namespace {
+    
+const size_t DEFAULT_RECORD_BUFFER_FRAMES = 44100;
+
+void resizeBufferAndShuffleChannels(ci::audio::BufferDynamic *buffer, size_t resultNumFrames)
+{
+    const size_t currentNumFrames = buffer->getNumFrames();
+    const size_t sampleSize = sizeof(ci::audio::BufferDynamic::SampleType);
+
+    if (currentNumFrames < resultNumFrames) {
+        // if expanding, resize and then shuffle. Make sure to get the data pointer after the resize.
+        buffer->setNumFrames(resultNumFrames);
+        float *data = buffer->getData();
+
+        for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
+            const size_t numZeroFrames = resultNumFrames - currentNumFrames;
+            const float *currentChannel = &data[ch * currentNumFrames];
+            float *resultChannel = &data[ch * resultNumFrames];
+
+            memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
+            memset(resultChannel - numZeroFrames, 0, numZeroFrames * sampleSize);
+        }
+    }
+    else if (currentNumFrames > resultNumFrames) {
+        // if shrinking, shuffle first and then resize.
+        float *data = buffer->getData();
+
+        for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
+            const float *currentChannel = &data[ch * currentNumFrames];
+            float *resultChannel = &data[ch * resultNumFrames];
+
+            memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
+        }
+
+        const size_t numZeroFrames = (currentNumFrames - resultNumFrames) * buffer->getNumChannels();
+        memset(data + buffer->getSize() - numZeroFrames, 0, numZeroFrames * sampleSize);
+
+        buffer->setNumFrames(resultNumFrames);
+    }
+}
+
+}
+
+
+BufferToWaveRecorderNode::BufferToWaveRecorderNode( std::size_t numChunks, double numSeconds )
+    : SampleRecorderNode( Format().channels( 1 ) ),
+    mLastOverrun( 0 ),
+    mNumChunks( numChunks ),
+    mNumSeconds( numSeconds ),
+    mRingBuffer( numChunks ),
+    mChunkMaxAudioVal( kMinAudioVal ),
+    mChunkMinAudioVal( kMaxAudioVal ),
+    mChunkSampleCounter( 0 ),
+    mChunkIndex( 0 )
+{
+    
+}
+
+void BufferToWaveRecorderNode::initialize()
+{
+    // adjust recorder buffer to match channels once initialized, since they could have changed since construction.
+    bool resize = mRecorderBuffer.getNumFrames() != 0;
+    mRecorderBuffer.setNumChannels( getNumChannels() );
+
+    // lenght of buffer is = number of seconds * sample rate 
+    initBuffers( size_t( mNumSeconds * (double)getSampleRate() ) ); 
+
+    // How many samples each chunk contains. That is it calculates the min and max of 
+    // This is calculated here and not in the initializer list because it uses getNumFrames()
+    // FIXME probably could be done in constructor body 
+    mNumSamplesPerChunk = std::lround( float( getNumFrames() ) / mNumChunks );
+
+    // if the buffer had already been resized, zero out any possibly existing data.
+    if( resize )
+        mRecorderBuffer.zero();
+
+    mEnvRampLen = kRampTime * getSampleRate();
+    mEnvDecayStart = mRecorderBuffer.getNumFrames() - mEnvRampLen;
+    if ( mEnvRampLen <= 0 ){
+        mEnvRampRate = 0;
+    }
+    else{
+        mEnvRampRate = 1.0f / mEnvRampLen;
+    }
+}
+
+void BufferToWaveRecorderNode::initBuffers(size_t numFrames)
+{
+    mRecorderBuffer.setSize( numFrames, getNumChannels() );
+    mCopiedBuffer = std::make_shared<ci::audio::BufferDynamic>( numFrames, getNumChannels() );
+}
+
+void BufferToWaveRecorderNode::start()
+{
+    mWritePos = 0;
+    mChunkIndex = 0;
+    enable();
+}
+
+void BufferToWaveRecorderNode::stop()
+{
+    disable();
+}
+
+void BufferToWaveRecorderNode::setNumSeconds(double numSeconds, bool shrinkToFit)
+{
+    setNumFrames(size_t(numSeconds * (double)getSampleRate()), shrinkToFit);
+}
+
+double BufferToWaveRecorderNode::getNumSeconds() const
+{
+    return (double)getNumFrames() / (double)getSampleRate();
+}
+
+void BufferToWaveRecorderNode::setNumFrames(size_t numFrames, bool shrinkToFit)
+{
+    if (mRecorderBuffer.getNumFrames() == numFrames)
+        return;
+
+    std::lock_guard<std::mutex> lock(getContext()->getMutex());
+
+    if (mWritePos != 0)
+        resizeBufferAndShuffleChannels(&mRecorderBuffer, numFrames);
+    else
+        mRecorderBuffer.setNumFrames(numFrames);
+
+    if (shrinkToFit)
+        mRecorderBuffer.shrinkToFit();
+}
+
+ci::audio::BufferRef BufferToWaveRecorderNode::getRecordedCopy() const
+{
+    // first grab the number of current frames, which may be increasing as the recording continues.
+    size_t numFrames = mWritePos;
+    mCopiedBuffer->setSize(numFrames, mRecorderBuffer.getNumChannels());
+
+    mCopiedBuffer->copy(mRecorderBuffer, numFrames);
+    return mCopiedBuffer;
+}
+
+void BufferToWaveRecorderNode::writeToFile(const ci::fs::path &filePath, ci::audio::SampleType sampleType)
+{
+    size_t currentWritePos = mWritePos;
+    ci::audio::BufferRef copiedBuffer = getRecordedCopy();
+
+    ci::audio::TargetFileRef target = ci::audio::TargetFile::create(filePath, getSampleRate(), getNumChannels(), sampleType);
+    target->write(copiedBuffer.get(), currentWritePos);
+}
+
+uint64_t BufferToWaveRecorderNode::getLastOverrun()
+{
+    uint64_t result = mLastOverrun;
+    mLastOverrun = 0;
+    return result;
+}
+
+
+void BufferToWaveRecorderNode::process(ci::audio::Buffer *buffer)
+{
+    size_t writePos = mWritePos;
+    size_t numWriteFrames = buffer->getNumFrames();
+
+    if ( writePos == 0 ){
+        RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_START, 0, 0, 0 );
+        mRingBuffer.write( &msg, 1 );
+
+        // reset everything
+        mChunkMinAudioVal = kMaxAudioVal;
+        mChunkMaxAudioVal = kMinAudioVal;
+        mChunkSampleCounter = 0;
+        mChunkIndex = 0;
+        mEnvRamp = 0.0f;
+    }
+
+    // if buffer has too many frames (because we're nearly at the end or at the end ) 
+    // of mRecoderBuffer then numWriteFrames becomes the number of samples left to 
+    // fill mRecorderBuffer. Which is 0 if the buffer is at the end.
+    if ( writePos + numWriteFrames > mRecorderBuffer.getNumFrames() )
+        numWriteFrames = mRecorderBuffer.getNumFrames() - writePos;
+
+    if ( numWriteFrames <= 0 )
+        return;
+
+
+    // apply envelope to the buffer at the edges to avoid clicks 
+    if ( writePos < mEnvRampLen ){ // beginning of wave 
+        for ( size_t i = 0; i < std::min( mEnvRampLen, numWriteFrames ); i++ ){
+            buffer->getData()[i] *= mEnvRamp;
+            mEnvRamp += mEnvRampRate;
+            if ( mEnvRamp > 1.0f )
+                mEnvRamp = 1.0f;
+        }
+    }
+    else if ( writePos + numWriteFrames > mEnvDecayStart ){ // end of wave 
+        for ( size_t i = std::max( writePos, mEnvDecayStart ) - writePos; i < numWriteFrames; i++ ){
+            buffer->getData()[i] *= mEnvRamp;
+            mEnvRamp -= mEnvRampRate;
+            if ( mEnvRamp < 0.0f )
+                mEnvRamp = 0.0f;
+        }
+    }
+
+
+    mRecorderBuffer.copyOffset(*buffer, numWriteFrames, writePos, 0);
+
+    if ( numWriteFrames < buffer->getNumFrames() )
+        mLastOverrun = getContext()->getNumProcessedFrames();
+
+    /* find max and minimum of this buffer */
+    for ( size_t i = 0; i < numWriteFrames; i++ ){
+
+        if ( buffer->getData()[i] < mChunkMinAudioVal ){
+            mChunkMinAudioVal = buffer->getData()[i];
+        }
+
+        if ( buffer->getData()[i] > mChunkMaxAudioVal ){
+            mChunkMaxAudioVal = buffer->getData()[i];
+        }
+
+        if ( mChunkSampleCounter >= mNumSamplesPerChunk              // if collected enough samples 
+            || writePos + i >= mRecorderBuffer.getNumFrames() - 1 ){ // or at the end of recorder buffer 
+            // send chunk to GUI
+            size_t chunkIndex = mChunkIndex.fetch_add( 1 );
+
+            RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_CHUNK, chunkIndex, mChunkMinAudioVal, mChunkMaxAudioVal );
+            mRingBuffer.write( &msg, 1 );
+            
+            // reset chunk info 
+            mChunkMinAudioVal = kMaxAudioVal;
+            mChunkMaxAudioVal = kMinAudioVal;
+            mChunkSampleCounter = 0;
+        }
+        else{
+            mChunkSampleCounter++;
+        }
+    }
+
+    // check if write position has been reset by the GUI thread, if not write new value
+    const size_t writePosNew = writePos + numWriteFrames;
+    mWritePos.compare_exchange_strong( writePos, writePosNew );
+
+}
+
+
+const float BufferToWaveRecorderNode::kMinAudioVal = -1.0f;
+const float BufferToWaveRecorderNode::kMaxAudioVal = 1.0f; 
+const float BufferToWaveRecorderNode::kRampTime = 0.02;
+
+