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( &parameters, 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