view 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 source
#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;