annotate CollidoscopeApp/src/BufferToWaveRecorderNode.cpp @ 18:f1ff1a81be20 tip

Changed licenses names. Fixed one comment and usage text in CollidoscopeApp.cpp.
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 25 Aug 2016 12:07:50 +0200
parents 75b744078d66
children
rev   line source
f@5 1 /*
f@5 2
f@5 3 Copyright (C) 2016 Queen Mary University of London
f@5 4 Author: Fiore Martin
f@5 5
f@5 6 This file is part of Collidoscope.
f@5 7
f@5 8 Collidoscope is free software: you can redistribute it and/or modify
f@5 9 it under the terms of the GNU General Public License as published by
f@5 10 the Free Software Foundation, either version 3 of the License, or
f@5 11 (at your option) any later version.
f@5 12
f@5 13 This program is distributed in the hope that it will be useful,
f@5 14 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@5 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@5 16 GNU General Public License for more details.
f@5 17
f@5 18 You should have received a copy of the GNU General Public License
f@5 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@5 20
f@5 21 This file incorporates work covered by the following copyright and permission notice:
f@5 22
f@5 23 Copyright (c) 2014, The Cinder Project
f@5 24
f@5 25 This code is intended to be used with the Cinder C++ library, http://libcinder.org
f@5 26
f@5 27 Redistribution and use in source and binary forms, with or without modification, are permitted provided that
f@5 28 the following conditions are met:
f@5 29
f@5 30 * Redistributions of source code must retain the above copyright notice, this list of conditions and
f@5 31 the following disclaimer.
f@5 32 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
f@5 33 the following disclaimer in the documentation and/or other materials provided with the distribution.
f@5 34
f@5 35 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
f@5 36 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
f@5 37 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
f@5 38 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
f@5 39 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
f@5 40 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
f@5 41 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
f@5 42 POSSIBILITY OF SUCH DAMAGE.
f@5 43
f@5 44 */
f@5 45
f@0 46 #include "BufferToWaveRecorderNode.h"
f@0 47 #include "cinder/audio/Context.h"
f@0 48 #include "cinder/audio/Target.h"
f@0 49
f@0 50
f@0 51
f@0 52 // ----------------------------------------------------------------------------------------------------
f@0 53 // MARK: - BufferRecorderNode
f@0 54 // ----------------------------------------------------------------------------------------------------
f@0 55
f@0 56 namespace {
f@0 57
f@0 58 const size_t DEFAULT_RECORD_BUFFER_FRAMES = 44100;
f@0 59
f@0 60 void resizeBufferAndShuffleChannels(ci::audio::BufferDynamic *buffer, size_t resultNumFrames)
f@0 61 {
f@0 62 const size_t currentNumFrames = buffer->getNumFrames();
f@0 63 const size_t sampleSize = sizeof(ci::audio::BufferDynamic::SampleType);
f@0 64
f@0 65 if (currentNumFrames < resultNumFrames) {
f@0 66 // if expanding, resize and then shuffle. Make sure to get the data pointer after the resize.
f@0 67 buffer->setNumFrames(resultNumFrames);
f@0 68 float *data = buffer->getData();
f@0 69
f@0 70 for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
f@0 71 const size_t numZeroFrames = resultNumFrames - currentNumFrames;
f@0 72 const float *currentChannel = &data[ch * currentNumFrames];
f@0 73 float *resultChannel = &data[ch * resultNumFrames];
f@0 74
f@0 75 memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
f@0 76 memset(resultChannel - numZeroFrames, 0, numZeroFrames * sampleSize);
f@0 77 }
f@0 78 }
f@0 79 else if (currentNumFrames > resultNumFrames) {
f@0 80 // if shrinking, shuffle first and then resize.
f@0 81 float *data = buffer->getData();
f@0 82
f@0 83 for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
f@0 84 const float *currentChannel = &data[ch * currentNumFrames];
f@0 85 float *resultChannel = &data[ch * resultNumFrames];
f@0 86
f@0 87 memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
f@0 88 }
f@0 89
f@0 90 const size_t numZeroFrames = (currentNumFrames - resultNumFrames) * buffer->getNumChannels();
f@0 91 memset(data + buffer->getSize() - numZeroFrames, 0, numZeroFrames * sampleSize);
f@0 92
f@0 93 buffer->setNumFrames(resultNumFrames);
f@0 94 }
f@0 95 }
f@0 96
f@0 97 }
f@0 98
f@0 99
f@0 100 BufferToWaveRecorderNode::BufferToWaveRecorderNode( std::size_t numChunks, double numSeconds )
f@0 101 : SampleRecorderNode( Format().channels( 1 ) ),
f@0 102 mLastOverrun( 0 ),
f@0 103 mNumChunks( numChunks ),
f@0 104 mNumSeconds( numSeconds ),
f@0 105 mRingBuffer( numChunks ),
f@0 106 mChunkMaxAudioVal( kMinAudioVal ),
f@0 107 mChunkMinAudioVal( kMaxAudioVal ),
f@0 108 mChunkSampleCounter( 0 ),
f@0 109 mChunkIndex( 0 )
f@0 110 {
f@0 111
f@0 112 }
f@0 113
f@0 114 void BufferToWaveRecorderNode::initialize()
f@0 115 {
f@0 116 // adjust recorder buffer to match channels once initialized, since they could have changed since construction.
f@0 117 bool resize = mRecorderBuffer.getNumFrames() != 0;
f@0 118 mRecorderBuffer.setNumChannels( getNumChannels() );
f@0 119
f@0 120 // lenght of buffer is = number of seconds * sample rate
f@0 121 initBuffers( size_t( mNumSeconds * (double)getSampleRate() ) );
f@0 122
f@0 123 // How many samples each chunk contains. That is it calculates the min and max of
f@0 124 // This is calculated here and not in the initializer list because it uses getNumFrames()
f@0 125 // FIXME probably could be done in constructor body
f@0 126 mNumSamplesPerChunk = std::lround( float( getNumFrames() ) / mNumChunks );
f@0 127
f@0 128 // if the buffer had already been resized, zero out any possibly existing data.
f@0 129 if( resize )
f@0 130 mRecorderBuffer.zero();
f@0 131
f@0 132 mEnvRampLen = kRampTime * getSampleRate();
f@0 133 mEnvDecayStart = mRecorderBuffer.getNumFrames() - mEnvRampLen;
f@0 134 if ( mEnvRampLen <= 0 ){
f@0 135 mEnvRampRate = 0;
f@0 136 }
f@0 137 else{
f@0 138 mEnvRampRate = 1.0f / mEnvRampLen;
f@0 139 }
f@0 140 }
f@0 141
f@0 142 void BufferToWaveRecorderNode::initBuffers(size_t numFrames)
f@0 143 {
f@0 144 mRecorderBuffer.setSize( numFrames, getNumChannels() );
f@0 145 mCopiedBuffer = std::make_shared<ci::audio::BufferDynamic>( numFrames, getNumChannels() );
f@0 146 }
f@0 147
f@0 148 void BufferToWaveRecorderNode::start()
f@0 149 {
f@0 150 mWritePos = 0;
f@0 151 mChunkIndex = 0;
f@0 152 enable();
f@0 153 }
f@0 154
f@0 155 void BufferToWaveRecorderNode::stop()
f@0 156 {
f@0 157 disable();
f@0 158 }
f@0 159
f@0 160 void BufferToWaveRecorderNode::setNumSeconds(double numSeconds, bool shrinkToFit)
f@0 161 {
f@0 162 setNumFrames(size_t(numSeconds * (double)getSampleRate()), shrinkToFit);
f@0 163 }
f@0 164
f@0 165 double BufferToWaveRecorderNode::getNumSeconds() const
f@0 166 {
f@0 167 return (double)getNumFrames() / (double)getSampleRate();
f@0 168 }
f@0 169
f@0 170 void BufferToWaveRecorderNode::setNumFrames(size_t numFrames, bool shrinkToFit)
f@0 171 {
f@0 172 if (mRecorderBuffer.getNumFrames() == numFrames)
f@0 173 return;
f@0 174
f@0 175 std::lock_guard<std::mutex> lock(getContext()->getMutex());
f@0 176
f@0 177 if (mWritePos != 0)
f@0 178 resizeBufferAndShuffleChannels(&mRecorderBuffer, numFrames);
f@0 179 else
f@0 180 mRecorderBuffer.setNumFrames(numFrames);
f@0 181
f@0 182 if (shrinkToFit)
f@0 183 mRecorderBuffer.shrinkToFit();
f@0 184 }
f@0 185
f@0 186 ci::audio::BufferRef BufferToWaveRecorderNode::getRecordedCopy() const
f@0 187 {
f@0 188 // first grab the number of current frames, which may be increasing as the recording continues.
f@0 189 size_t numFrames = mWritePos;
f@0 190 mCopiedBuffer->setSize(numFrames, mRecorderBuffer.getNumChannels());
f@0 191
f@0 192 mCopiedBuffer->copy(mRecorderBuffer, numFrames);
f@0 193 return mCopiedBuffer;
f@0 194 }
f@0 195
f@0 196 void BufferToWaveRecorderNode::writeToFile(const ci::fs::path &filePath, ci::audio::SampleType sampleType)
f@0 197 {
f@0 198 size_t currentWritePos = mWritePos;
f@0 199 ci::audio::BufferRef copiedBuffer = getRecordedCopy();
f@0 200
f@0 201 ci::audio::TargetFileRef target = ci::audio::TargetFile::create(filePath, getSampleRate(), getNumChannels(), sampleType);
f@0 202 target->write(copiedBuffer.get(), currentWritePos);
f@0 203 }
f@0 204
f@0 205 uint64_t BufferToWaveRecorderNode::getLastOverrun()
f@0 206 {
f@0 207 uint64_t result = mLastOverrun;
f@0 208 mLastOverrun = 0;
f@0 209 return result;
f@0 210 }
f@0 211
f@0 212
f@0 213 void BufferToWaveRecorderNode::process(ci::audio::Buffer *buffer)
f@0 214 {
f@0 215 size_t writePos = mWritePos;
f@0 216 size_t numWriteFrames = buffer->getNumFrames();
f@0 217
f@0 218 if ( writePos == 0 ){
f@0 219 RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_START, 0, 0, 0 );
f@0 220 mRingBuffer.write( &msg, 1 );
f@0 221
f@0 222 // reset everything
f@0 223 mChunkMinAudioVal = kMaxAudioVal;
f@0 224 mChunkMaxAudioVal = kMinAudioVal;
f@0 225 mChunkSampleCounter = 0;
f@0 226 mChunkIndex = 0;
f@0 227 mEnvRamp = 0.0f;
f@0 228 }
f@0 229
f@0 230 // if buffer has too many frames (because we're nearly at the end or at the end )
f@0 231 // of mRecoderBuffer then numWriteFrames becomes the number of samples left to
f@0 232 // fill mRecorderBuffer. Which is 0 if the buffer is at the end.
f@0 233 if ( writePos + numWriteFrames > mRecorderBuffer.getNumFrames() )
f@0 234 numWriteFrames = mRecorderBuffer.getNumFrames() - writePos;
f@0 235
f@0 236 if ( numWriteFrames <= 0 )
f@0 237 return;
f@0 238
f@0 239
f@0 240 // apply envelope to the buffer at the edges to avoid clicks
f@0 241 if ( writePos < mEnvRampLen ){ // beginning of wave
f@0 242 for ( size_t i = 0; i < std::min( mEnvRampLen, numWriteFrames ); i++ ){
f@0 243 buffer->getData()[i] *= mEnvRamp;
f@0 244 mEnvRamp += mEnvRampRate;
f@0 245 if ( mEnvRamp > 1.0f )
f@0 246 mEnvRamp = 1.0f;
f@0 247 }
f@0 248 }
f@0 249 else if ( writePos + numWriteFrames > mEnvDecayStart ){ // end of wave
f@0 250 for ( size_t i = std::max( writePos, mEnvDecayStart ) - writePos; i < numWriteFrames; i++ ){
f@0 251 buffer->getData()[i] *= mEnvRamp;
f@0 252 mEnvRamp -= mEnvRampRate;
f@0 253 if ( mEnvRamp < 0.0f )
f@0 254 mEnvRamp = 0.0f;
f@0 255 }
f@0 256 }
f@0 257
f@0 258
f@0 259 mRecorderBuffer.copyOffset(*buffer, numWriteFrames, writePos, 0);
f@0 260
f@0 261 if ( numWriteFrames < buffer->getNumFrames() )
f@0 262 mLastOverrun = getContext()->getNumProcessedFrames();
f@0 263
f@0 264 /* find max and minimum of this buffer */
f@0 265 for ( size_t i = 0; i < numWriteFrames; i++ ){
f@0 266
f@0 267 if ( buffer->getData()[i] < mChunkMinAudioVal ){
f@0 268 mChunkMinAudioVal = buffer->getData()[i];
f@0 269 }
f@0 270
f@0 271 if ( buffer->getData()[i] > mChunkMaxAudioVal ){
f@0 272 mChunkMaxAudioVal = buffer->getData()[i];
f@0 273 }
f@0 274
f@0 275 if ( mChunkSampleCounter >= mNumSamplesPerChunk // if collected enough samples
f@0 276 || writePos + i >= mRecorderBuffer.getNumFrames() - 1 ){ // or at the end of recorder buffer
f@0 277 // send chunk to GUI
f@0 278 size_t chunkIndex = mChunkIndex.fetch_add( 1 );
f@0 279
f@0 280 RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_CHUNK, chunkIndex, mChunkMinAudioVal, mChunkMaxAudioVal );
f@0 281 mRingBuffer.write( &msg, 1 );
f@0 282
f@0 283 // reset chunk info
f@0 284 mChunkMinAudioVal = kMaxAudioVal;
f@0 285 mChunkMaxAudioVal = kMinAudioVal;
f@0 286 mChunkSampleCounter = 0;
f@0 287 }
f@0 288 else{
f@0 289 mChunkSampleCounter++;
f@0 290 }
f@0 291 }
f@0 292
f@0 293 // check if write position has been reset by the GUI thread, if not write new value
f@0 294 const size_t writePosNew = writePos + numWriteFrames;
f@0 295 mWritePos.compare_exchange_strong( writePos, writePosNew );
f@0 296
f@0 297 }
f@0 298
f@0 299
f@0 300 const float BufferToWaveRecorderNode::kMinAudioVal = -1.0f;
f@0 301 const float BufferToWaveRecorderNode::kMaxAudioVal = 1.0f;
f@0 302 const float BufferToWaveRecorderNode::kRampTime = 0.02;
f@0 303
f@0 304