@ -5,82 +5,217 @@
* Extended from on Teensy Audio Library
*
* License : MIT License . Use at your own risk .
* Rebuilt Feb 2023 to include stall / non - stall behavior and max buffers .
* This is slightly adapted from the updated play_queue in the I16
* Teensy Audio library . Thanks to Jonathan Oakley for the improvements .
* See play_queue_f32 . h for more details
*/
# include "play_queue_f32.h"
# include "utility/dspinst.h"
//#include "utility/dspinst.h"
// =================================================
//#include <Arduino.h>
void AudioPlayQueue_F32 : : setMaxBuffers ( uint8_t maxb )
{
if ( maxb < 2 )
maxb = 2 ;
if ( maxb > MAX_BUFFERS )
maxb = MAX_BUFFERS ;
max_buffers = maxb ;
}
bool AudioPlayQueue_F32 : : available ( void )
{
if ( userblock ) return true ;
userblock = allocate_f32 ( ) ;
if ( userblock ) return true ;
return false ;
if ( userblock ) return true ;
userblock = AudioStream_F32 : : allocate_f32 ( ) ;
if ( userblock ) return true ;
return false ;
}
/* getBuffer() returns a pointer to the data area of an AudioBlock_32
* that can be loaded in the . INO . There is only one of these at a
* time , and they hold 128 float32_t . allocate_f32 will hold up
* a return from getBuffer ( ) if Audio memory is not available . This will
* be freed up by update ( ) .
/* Get address of current data buffer, newly allocated if necessary.
* With behaviour = = ORIGINAL this will stall ( calling yield ( ) ) until an audio block
* becomes available - there ' s no real guarantee this will ever happen . . .
* With behaviour = = NON_STALLING this will never stall , and will conform to the published
* API by returning NULL if no audio block is available .
* return : NULL if buffer not available , else pointer to buffer
* of AUDIO_BLOCK_SAMPLES of float32_t
*/
float32_t * AudioPlayQueue_F32 : : getBuffer ( void )
float32_t * AudioPlayQueue_F32 : : getBuffer ( void )
{
if ( userblock ) return userblock - > data ;
while ( 1 ) {
userblock = allocate_f32 ( ) ;
if ( userblock ) return userblock - > data ;
yield ( ) ;
}
if ( NULL = = userblock ) // not got one: try to get one
{
switch ( behaviour )
{
default :
while ( 1 )
{
userblock = AudioStream_F32 : : allocate_f32 ( ) ;
if ( userblock )
break ;
yield ( ) ;
}
break ;
case NON_STALLING :
userblock = AudioStream_F32 : : allocate_f32 ( ) ;
break ;
}
}
return userblock = = NULL
? NULL
: userblock - > data ;
}
/* playBuffer() can be called anytime after data is
* loaded to the data block pointed to by getBuffer ) .
* This function then enters the pointer to the queue ,
* waiting to be sent in turn . If the queue is full ,
* this function waits until a spot in the queue is opened
* up by update ( ) ( called by interrupts ) .
*/
void AudioPlayQueue_F32 : : playBuffer ( void )
/* Queue userblock for later playback in update().
* If there ' s no user block in use then we presume success : this means it ' s
* safe to keep calling playBuffer ( ) regularly even if we ' ve not been
* creating audio to be played .
* return 0 for success , 1 for re - try required */
uint32_t AudioPlayQueue_F32 : : playBuffer ( void )
{
uint32_t h ;
uint32_t result = 0 ;
uint32_t h ;
if ( ! userblock ) return ;
h = head + 1 ;
if ( h > = 32 ) h = 0 ;
while ( tail = = h ) ; // wait until space in the queue
queue [ h ] = userblock ;
head = h ;
userblock = NULL ;
if ( userblock ) // only need to queue if we have a user block!
{
// Find place for next queue entry
h = head + 1 ;
if ( h > = max_buffers ) h = 0 ;
// Wait for space, or return "please re-try", depending on behaviour
switch ( behaviour )
{
default :
while ( tail = = h ) ; // wait until space in the queue
break ;
case NON_STALLING :
if ( tail = = h ) // if no space...
result = 1 ; // ...return 1: user code must re-try later
break ;
}
if ( 0 = = result )
{
queue [ h ] = userblock ; // block is queued for transmission
head = h ; // head has changed
userblock = NULL ; // block no longer available for filling
}
}
return result ;
}
void AudioPlayQueue_F32 : : update ( void )
/* Put a single sample to buffer, and queue if buffer full.
* return 0 for success ; 1 : failed , data not stored , call again
* with same data . */
uint32_t AudioPlayQueue_F32 : : play ( float32_t data )
{
audio_block_f32_t * block ;
uint32_t t ;
t = tail ;
if ( t ! = head ) { // a data block is available to transmit out
if ( + + t > = 32 ) t = 0 ; // tail is advanced by one, circularly
block = queue [ t ] ; // pointer to next block
tail = t ;
transmit ( block ) ;
release ( block ) ;
}
uint32_t result = 1 ;
float32_t * buf = getBuffer ( ) ;
do
{
if ( NULL = = buf ) // no buffer, failed already
break ;
if ( uptr > = AUDIO_BLOCK_SAMPLES ) // buffer is full, we're re-called: try again
{
if ( 0 = = playBuffer ( ) ) // success emitting old buffer...
{
uptr = 0 ; // ...start at beginning...
buf = getBuffer ( ) ; // ...of new buffer
continue ; // loop to check buffer and store the sample
}
}
else // non-full buffer
{
buf [ uptr + + ] = data ;
result = 0 ;
if ( uptr > = AUDIO_BLOCK_SAMPLES // buffer is full...
& & 0 = = playBuffer ( ) ) // ... try to queue it
uptr = 0 ; // success!
}
} while ( false ) ;
return result ;
}
//assume user already has an audio_block that was NOT allocated by this
//playBuffer. Here, you hand it your buffer. This object takes ownership
//of it and puts it into the queue
/*
* Put multiple samples to buffer ( s ) , and queue if buffer ( s ) full .
* return 0 for success ; > 0 : failed , data not stored , call again with
* remaining data ( return is unused data length ) */
uint32_t AudioPlayQueue_F32 : : play ( const float32_t * data , uint32_t len )
{
uint32_t result = len ;
float32_t * buf = getBuffer ( ) ;
do
{
unsigned int avail_in_userblock = AUDIO_BLOCK_SAMPLES - uptr ;
unsigned int to_copy = avail_in_userblock > len ? len : avail_in_userblock ;
if ( NULL = = buf ) // no buffer, failed
break ;
if ( uptr > = AUDIO_BLOCK_SAMPLES ) // buffer is full, we're re-called: try again
{
if ( 0 = = playBuffer ( ) ) // success emitting old buffer...
{
uptr = 0 ; // ...start at beginning...
buf = getBuffer ( ) ; // ...of new buffer
continue ; // loop to check buffer and store more samples
}
}
if ( 0 = = len ) // nothing left to do
break ;
// we have a buffer and something to copy to it: do that
memcpy ( ( void * ) ( buf + uptr ) , ( void * ) data , to_copy * sizeof ( float32_t ) ) ;
uptr + = to_copy ;
data + = to_copy ;
len - = to_copy ;
result - = to_copy ;
if ( uptr > = AUDIO_BLOCK_SAMPLES ) // buffer is full...
{
if ( 0 = = playBuffer ( ) ) // ... try to queue it
{
uptr = 0 ; // success!
if ( len > 0 ) // more to buffer...
buf = getBuffer ( ) ; // ...try to do that
}
else
break ; // queue failed: exit and try again later
}
} while ( len > 0 ) ;
return result ;
}
// Assume user already has an audio_block that was NOT allocated by this
// playBuffer. Here, you hand it your buffer. This object takes ownership
// of it and puts it into the queue. This is not in I16 library.
void AudioPlayQueue_F32 : : playAudioBlock ( audio_block_f32_t * audio_block ) {
uint32_t h ;
if ( ! audio_block ) return ;
h = head + 1 ;
if ( h > = 32 ) h = 0 ;
while ( tail = = h ) ; // wait until space in the queue
if ( h > = max_buffers ) h = 0 ;
while ( tail = = h ) ; // wait until space in the queue
queue [ h ] = audio_block ;
audio_block - > ref_count + + ; //take ownership of this block
head = h ;
//userblock = NULL;
userblock = NULL ;
}
void AudioPlayQueue_F32 : : update ( void )
{
audio_block_f32_t * block ;
uint32_t t ;
t = tail ;
if ( t ! = head ) { // a data block is available to transmit out
if ( + + t > = max_buffers ) t = 0 ; // tail is advanced 1, circularly
block = queue [ t ] ; // pointer to next block
tail = t ;
AudioStream_F32 : : transmit ( block ) ;
AudioStream_F32 : : release ( block ) ; // we've lost interest in this block...
queue [ t ] = NULL ; // ...forget it here, too
}
}