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