-
Notifications
You must be signed in to change notification settings - Fork 182
/
RtWvOut.cpp
217 lines (181 loc) · 6.73 KB
/
RtWvOut.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/***************************************************/
/*! \class RtWvOut
\brief STK realtime audio (blocking) output class.
This class provides a simplified interface to RtAudio for realtime
audio output. It is a subclass of WvOut. This class makes use of
RtAudio's callback functionality by creating a large ring-buffer
into which data is written. This class should not be used when
low-latency is desired.
RtWvOut supports multi-channel data in interleaved format. It is
important to distinguish the tick() method that outputs a single
sample to all channels in a sample frame from the overloaded one
that takes a reference to an StkFrames object for multi-channel
and/or multi-frame data.
by Perry R. Cook and Gary P. Scavone, 1995--2023.
*/
/***************************************************/
#include "RtWvOut.h"
#include <cstring>
namespace stk {
// Streaming status states.
enum { RUNNING, EMPTYING, FINISHED };
// This function is automatically called by RtAudio to get audio data for output.
int write( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
double streamTime, RtAudioStreamStatus status, void *dataPointer )
{
return ( (RtWvOut *) dataPointer )->readBuffer( outputBuffer, nBufferFrames );
}
// This function does not block. If the user does not write output
// data to the buffer fast enough, previous data will be re-output
// (data underrun).
int RtWvOut :: readBuffer( void *buffer, unsigned int frameCount )
{
unsigned int nSamples, nChannels = data_.channels();
unsigned int nFrames = frameCount;
StkFloat *input = (StkFloat *) &data_[ readIndex_ * nChannels ];
StkFloat *output = (StkFloat *) buffer;
long counter;
while ( nFrames > 0 ) {
// I'm assuming that both the RtAudio and StkFrames buffers
// contain interleaved data.
counter = nFrames;
// Pre-increment read pointer and check bounds.
readIndex_ += nFrames;
if ( readIndex_ >= data_.frames() ) {
counter -= readIndex_ - data_.frames();
readIndex_ = 0;
}
// Copy data from the StkFrames container.
if ( status_ == EMPTYING && framesFilled_ <= counter ) {
nSamples = framesFilled_ * nChannels;
unsigned int i;
for ( i=0; i<nSamples; i++ ) *output++ = *input++;
nSamples = (counter - framesFilled_) * nChannels;
for ( i=0; i<nSamples; i++ ) *output++ = 0.0;
status_ = FINISHED;
return 1;
}
else {
nSamples = counter * nChannels;
for ( unsigned int i=0; i<nSamples; i++ )
*output++ = *input++;
}
nFrames -= counter;
}
mutex_.lock();
framesFilled_ -= frameCount;
mutex_.unlock();
if ( framesFilled_ < 0 ) {
framesFilled_ = 0;
// writeIndex_ = readIndex_;
oStream_ << "RtWvOut: audio buffer underrun!";
handleError( StkError::WARNING );
}
return 0;
}
RtWvOut :: RtWvOut( unsigned int nChannels, StkFloat sampleRate, int deviceIndex, int bufferFrames, int nBuffers )
: stopped_( true ), readIndex_( 0 ), writeIndex_( 0 ), framesFilled_( 0 ), status_(0)
{
std::vector<unsigned int> deviceIds = dac_.getDeviceIds();
if ( deviceIds.size() < 1 )
handleError( "RtWvOut: No audio devices found!", StkError::AUDIO_SYSTEM );
// We'll let RtAudio deal with channel and sample rate limitations.
RtAudio::StreamParameters parameters;
if ( deviceIndex == 0 )
parameters.deviceId = dac_.getDefaultOutputDevice();
else {
if ( deviceIndex >= deviceIds.size() )
handleError( "RtWvOut: Device index is invalid.", StkError::AUDIO_SYSTEM );
parameters.deviceId = deviceIds[deviceIndex-1];
}
parameters.nChannels = nChannels;
unsigned int size = bufferFrames;
RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
// Open a stream and set the callback function.
if ( dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this ) ) {
handleError( dac_.getErrorText(), StkError::AUDIO_SYSTEM );
}
data_.resize( size * nBuffers, nChannels );
// Start writing half-way into buffer.
writeIndex_ = (unsigned int ) (data_.frames() / 2.0);
framesFilled_ = writeIndex_;
}
RtWvOut :: ~RtWvOut( void )
{
// Change status flag to signal callback to clear the buffer and close.
status_ = EMPTYING;
while ( status_ != FINISHED && dac_.isStreamRunning() == true ) Stk::sleep( 100 );
dac_.closeStream();
}
void RtWvOut :: start( void )
{
if ( stopped_ ) {
dac_.startStream();
stopped_ = false;
}
}
void RtWvOut :: stop( void )
{
if ( !stopped_ ) {
dac_.stopStream();
stopped_ = true;
}
}
void RtWvOut :: tick( const StkFloat sample )
{
if ( stopped_ ) this->start();
// Block until we have room for at least one frame of output data.
while ( framesFilled_ == (long) data_.frames() ) Stk::sleep( 1 );
unsigned int nChannels = data_.channels();
StkFloat input = sample;
clipTest( input );
unsigned long index = writeIndex_ * nChannels;
for ( unsigned int j=0; j<nChannels; j++ )
data_[index++] = input;
mutex_.lock();
framesFilled_++;
mutex_.unlock();
frameCounter_++;
writeIndex_++;
if ( writeIndex_ == data_.frames() )
writeIndex_ = 0;
}
void RtWvOut :: tick( const StkFrames& frames )
{
#if defined(_STK_DEBUG_)
if ( data_.channels() != frames.channels() ) {
oStream_ << "RtWvOut::tick(): incompatible channel value in StkFrames argument!";
handleError( StkError::FUNCTION_ARGUMENT );
}
#endif
if ( stopped_ ) this->start();
// See how much space we have and fill as much as we can ... if we
// still have samples left in the frames object, then wait and
// repeat.
unsigned int framesEmpty, nFrames, bytes, framesWritten = 0;
unsigned int nChannels = data_.channels();
while ( framesWritten < frames.frames() ) {
// Block until we have some room for output data.
while ( framesFilled_ == (long) data_.frames() ) Stk::sleep( 1 );
framesEmpty = data_.frames() - framesFilled_;
// Copy data in one chunk up to the end of the data buffer.
nFrames = framesEmpty;
if ( writeIndex_ + nFrames > data_.frames() )
nFrames = data_.frames() - writeIndex_;
if ( nFrames > frames.frames() - framesWritten )
nFrames = frames.frames() - framesWritten;
bytes = nFrames * nChannels * sizeof( StkFloat );
StkFloat *samples = &data_[writeIndex_ * nChannels];
StkFrames *ins = (StkFrames *) &frames;
memcpy( samples, &(*ins)[framesWritten * nChannels], bytes );
for ( unsigned int i=0; i<nFrames * nChannels; i++ ) clipTest( *samples++ );
writeIndex_ += nFrames;
if ( writeIndex_ == data_.frames() ) writeIndex_ = 0;
framesWritten += nFrames;
mutex_.lock();
framesFilled_ += nFrames;
mutex_.unlock();
frameCounter_ += nFrames;
}
}
} // stk namespace