mirror of
https://github.com/thestk/stk
synced 2026-01-12 04:21:52 +00:00
214 lines
6.4 KiB
C++
214 lines
6.4 KiB
C++
/***************************************************/
|
|
/*! \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--2019.
|
|
*/
|
|
/***************************************************/
|
|
|
|
#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 device, int bufferFrames, int nBuffers )
|
|
: stopped_( true ), readIndex_( 0 ), writeIndex_( 0 ), framesFilled_( 0 ), status_(0)
|
|
{
|
|
// We'll let RtAudio deal with channel and sample rate limitations.
|
|
RtAudio::StreamParameters parameters;
|
|
if ( device == 0 )
|
|
parameters.deviceId = dac_.getDefaultOutputDevice();
|
|
else
|
|
parameters.deviceId = device - 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.
|
|
try {
|
|
dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this );
|
|
}
|
|
catch ( RtAudioError &error ) {
|
|
handleError( error.what(), 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
|