mirror of
https://github.com/thestk/stk
synced 2026-01-14 13:31:53 +00:00
Version 4.3.0
This commit is contained in:
committed by
Stephen Sinclair
parent
2cbce2d8bd
commit
27d9b79dc7
208
src/RtWvOut.cpp
208
src/RtWvOut.cpp
@@ -3,62 +3,124 @@
|
||||
\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. Because this class
|
||||
makes use of RtAudio's blocking output routines, its performance
|
||||
is less robust on systems where the audio API is callback-based
|
||||
(Macintosh CoreAudio and Windows ASIO).
|
||||
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() methods, which output single
|
||||
samples to all channels in a sample frame, from the tickFrame()
|
||||
method, which take a pointer or reference to multi-channel sample
|
||||
frame data.
|
||||
RtWvOut supports multi-channel data in both interleaved and
|
||||
non-interleaved formats. It is important to distinguish the
|
||||
tick() methods, which output single samples to all channels in a
|
||||
sample frame, from the tickFrame() method, which take a pointer or
|
||||
reference to multi-channel sample frame data.
|
||||
|
||||
by Perry R. Cook and Gary P. Scavone, 1995 - 2005.
|
||||
by Perry R. Cook and Gary P. Scavone, 1995 - 2007.
|
||||
*/
|
||||
/***************************************************/
|
||||
|
||||
#include "RtWvOut.h"
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
framesFilled_ -= frameCount;
|
||||
if ( framesFilled_ < 0 ) {
|
||||
framesFilled_ = 0;
|
||||
// writeIndex_ = readIndex_;
|
||||
errorString_ << "RtWvOut: audio buffer underrun!";
|
||||
handleError( StkError::WARNING );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
RtWvOut :: RtWvOut( unsigned int nChannels, StkFloat sampleRate, int device, int bufferFrames, int nBuffers )
|
||||
: stopped_( true ), nChannels_(nChannels), bufferIndex_( 0 ), iBuffer_( 0 )
|
||||
: stopped_( true ), readIndex_( 0 ), writeIndex_( 0 ), framesFilled_( 0 ), status_(0)
|
||||
{
|
||||
// We'll let RtAudio deal with channel and sample rate limitations.
|
||||
int size = bufferFrames;
|
||||
RtAudio::StreamParameters parameters;
|
||||
parameters.deviceId = device;
|
||||
parameters.nChannels = nChannels;
|
||||
unsigned int size = bufferFrames;
|
||||
RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
|
||||
|
||||
dac_ = 0;
|
||||
// Open a stream and set the callback function.
|
||||
try {
|
||||
dac_ = new RtAudio();
|
||||
dac_.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &size, &write, (void *)this );
|
||||
}
|
||||
catch (RtError &error) {
|
||||
handleError( error.getMessageString(), StkError::AUDIO_SYSTEM );
|
||||
catch ( RtError &error ) {
|
||||
handleError( error.what(), StkError::AUDIO_SYSTEM );
|
||||
}
|
||||
|
||||
// Now open a stream and get the buffer pointer.
|
||||
try {
|
||||
dac_->openStream( device, (int)nChannels, 0, 0, format,
|
||||
(int)sampleRate, &size, nBuffers );
|
||||
buffer_ = (StkFloat *) dac_->getStreamBuffer();
|
||||
}
|
||||
catch (RtError &error) {
|
||||
handleError( error.getMessageString(), StkError::AUDIO_SYSTEM );
|
||||
}
|
||||
|
||||
bufferFrames_ = size;
|
||||
data_.resize( size * nBuffers, nChannels );
|
||||
unsigned int offset = (unsigned int ) (data_.size() / 2.0);
|
||||
writeIndex_ = offset; // start writing half-way into buffer
|
||||
framesFilled_ = offset;
|
||||
}
|
||||
|
||||
RtWvOut :: ~RtWvOut()
|
||||
{
|
||||
if ( !stopped_ ) dac_->stopStream();
|
||||
dac_->closeStream();
|
||||
delete dac_;
|
||||
// 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()
|
||||
{
|
||||
if ( stopped_ ) {
|
||||
dac_->startStream();
|
||||
dac_.startStream();
|
||||
stopped_ = false;
|
||||
}
|
||||
}
|
||||
@@ -66,77 +128,81 @@ void RtWvOut :: start()
|
||||
void RtWvOut :: stop()
|
||||
{
|
||||
if ( !stopped_ ) {
|
||||
dac_->stopStream();
|
||||
dac_.stopStream();
|
||||
stopped_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RtWvOut :: incrementFrame( void )
|
||||
{
|
||||
frameCounter_++;
|
||||
bufferIndex_++;
|
||||
|
||||
if ( bufferIndex_ == bufferFrames_ ) {
|
||||
try {
|
||||
dac_->tickStream();
|
||||
}
|
||||
catch (RtError &error) {
|
||||
handleError( error.getMessageString(), StkError::AUDIO_SYSTEM );
|
||||
}
|
||||
bufferIndex_ = 0;
|
||||
iBuffer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RtWvOut :: computeSample( const StkFloat sample )
|
||||
{
|
||||
if ( stopped_ ) start();
|
||||
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 );
|
||||
for ( unsigned int j=0; j<nChannels_; j++ )
|
||||
buffer_[iBuffer_++] = input;
|
||||
unsigned long index = writeIndex_ * nChannels;
|
||||
for ( unsigned int j=0; j<nChannels; j++ )
|
||||
data_[index++] = input;
|
||||
|
||||
this->incrementFrame();
|
||||
framesFilled_++;
|
||||
frameCounter_++;
|
||||
writeIndex_++;
|
||||
if ( writeIndex_ == data_.frames() )
|
||||
writeIndex_ = 0;
|
||||
}
|
||||
|
||||
void RtWvOut :: computeFrames( const StkFrames& frames )
|
||||
{
|
||||
if ( stopped_ ) start();
|
||||
|
||||
if ( frames.channels() != nChannels_ ) {
|
||||
if ( data_.channels() != frames.channels() ) {
|
||||
errorString_ << "RtWvOut::computeFrames(): incompatible channel value in StkFrames argument!";
|
||||
handleError( StkError::FUNCTION_ARGUMENT );
|
||||
}
|
||||
|
||||
unsigned int j;
|
||||
if ( nChannels_ == 1 || frames.interleaved() ) {
|
||||
if ( stopped_ ) this->start();
|
||||
|
||||
unsigned int iFrames = 0;
|
||||
// Block until we have room for the frames of output data.
|
||||
while ( data_.frames() - framesFilled_ < frames.frames() ) Stk::sleep( 1 );
|
||||
|
||||
unsigned int j, nChannels = data_.channels();
|
||||
if ( nChannels == 1 || frames.interleaved() ) {
|
||||
|
||||
unsigned int index, iFrames = 0;
|
||||
for ( unsigned int i=0; i<frames.frames(); i++ ) {
|
||||
|
||||
for ( j=0; j<nChannels_; j++ ) {
|
||||
buffer_[iBuffer_] = frames[iFrames++];
|
||||
clipTest( buffer_[iBuffer_++] );
|
||||
}
|
||||
index = writeIndex_ * nChannels;
|
||||
for ( j=0; j<nChannels; j++ )
|
||||
data_[index] = frames[iFrames++];
|
||||
clipTest( data_[index++] );
|
||||
|
||||
this->incrementFrame();
|
||||
framesFilled_++;
|
||||
frameCounter_++;
|
||||
writeIndex_++;
|
||||
if ( writeIndex_ == data_.frames() )
|
||||
writeIndex_ = 0;
|
||||
}
|
||||
}
|
||||
else { // non-interleaved frames
|
||||
|
||||
unsigned long hop = frames.frames();
|
||||
unsigned int index;
|
||||
unsigned int index, iFrame;
|
||||
for ( unsigned int i=0; i<frames.frames(); i++ ) {
|
||||
|
||||
index = i;
|
||||
for ( j=0; j<nChannels_; j++ ) {
|
||||
buffer_[iBuffer_] = frames[index];
|
||||
clipTest( buffer_[iBuffer_++] );
|
||||
index += hop;
|
||||
iFrame = i;
|
||||
index = writeIndex_ * nChannels;
|
||||
for ( j=0; j<nChannels; j++ ) {
|
||||
data_[index] = frames[iFrame];
|
||||
clipTest( data_[index++] );
|
||||
iFrame += hop;
|
||||
}
|
||||
|
||||
this->incrementFrame();
|
||||
framesFilled_++;
|
||||
frameCounter_++;
|
||||
writeIndex_++;
|
||||
if ( writeIndex_ == data_.frames() )
|
||||
writeIndex_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user