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