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