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