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