To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / CollidoscopeApp / src / BufferToWaveRecorderNode.cpp @ 4:ab6db404403a

History | View | Annotate | Download (8.6 KB)

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