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