Mercurial > hg > opencollidoscope
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 |