/*
*   AudioRecordQueue_F32
*
*   Created: Chip Audette (OpenAudio), Feb 2017
*       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"

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 = AudioStream_F32::allocate_f32();
        if (userblock) return true;
        return false;
}

/* 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)
{
	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;
}

/* 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 result = 0;
	uint32_t h;

	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;
}

/* 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)
{
	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;
}

/*
 * 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 >= 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;
}

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
	}
}