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 @ 0:02467299402e

History | View | Annotate | Download (8.6 KB)

1 0:02467299402e f
#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