From d5419e0af180128d249c690d394f771ec2a6bb1c Mon Sep 17 00:00:00 2001 From: boblark Date: Mon, 27 Feb 2023 11:55:16 -0800 Subject: [PATCH] Major addition of functions from J. Oakley per I16 library. Thanks! --- play_queue_f32.cpp | 237 +++++++++++++++++++++++++++++++++++---------- play_queue_f32.h | 113 +++++++++++++-------- 2 files changed, 256 insertions(+), 94 deletions(-) diff --git a/play_queue_f32.cpp b/play_queue_f32.cpp index 76ad25e..569b78f 100644 --- a/play_queue_f32.cpp +++ b/play_queue_f32.cpp @@ -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 + +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 + } } diff --git a/play_queue_f32.h b/play_queue_f32.h index 420e15b..bc1cb8c 100644 --- a/play_queue_f32.h +++ b/play_queue_f32.h @@ -1,29 +1,39 @@ -/* -* AudioPlayQueue_F32 -* -* Created: Chip Audette (OpenAudio), Feb 2017 -* Extended from on Teensy Audio Library -* -* License: MIT License. Use at your own risk. -* -*/ -/* Notes from Paul Stoffregen (from 4320 LED Video+Sound Project) - * - * AudioPlayQueue - Play audio data provided by the Arduino sketch. - * This object provides functions to allow the sketch code to push data - * into the audio system. - * - * getBuffer(); - * Returns a pointer to an array of 128 int16. - * This buffer is within the audio library memory pool, providing the most - * efficient way to input data to the audio system. The buffer is likely - * to be populated by previously used data, so the entire 128 words should - * be written before calling playBuffer(). Only a single buffer should be - * requested at a time. This function may return NULL if no memory - * is available. - * - * playBuffer(); - * Transmit the buffer previously obtained from getBuffer(). +/* + * AudioPlayQueue_F32 + * + * Created: Chip Audette (OpenAudio), Feb 2017 + * Extended from Audio Library for Teensy 3.X + * 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 toJonathan Oakley for the improvements. + * Bob Larkin bob@janbob.com + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef play_queue_f32_h_ @@ -35,26 +45,43 @@ class AudioPlayQueue_F32 : public AudioStream_F32 { //GUI: inputs:0, outputs:1 //this line used for automatic generation of GUI node +private: +#if defined(__IMXRT1062__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) + static const unsigned int MAX_BUFFERS = 80; +#else + static const unsigned int MAX_BUFFERS = 32; +#endif + public: AudioPlayQueue_F32(void) : AudioStream_F32(0, NULL), - userblock(NULL), head(0), tail(0) { } + userblock(NULL), uptr(0), head(0), tail(0), max_buffers(MAX_BUFFERS) { } AudioPlayQueue_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL), - userblock(NULL), head(0), tail(0) { } - //void play(int16_t data); - //void play(const int16_t *data, uint32_t len); - //void play(float32_t data); - //void play(const float32_t *data, uint32_t len); - void playAudioBlock(audio_block_f32_t *); // Not in I16 library - bool available(void); - float32_t * getBuffer(void); - void playBuffer(void); - void stop(void); - //bool isPlaying(void) { return playing; } - virtual void update(void); + userblock(NULL), uptr(0), head(0), tail(0), max_buffers(MAX_BUFFERS) { } + + uint32_t play(float32_t data); + uint32_t play(const float32_t *data, uint32_t len); + void playAudioBlock(audio_block_f32_t *audio_block); + bool available(void); + // Returns a pointer to an array of AUDIO_BLOCK_SAMPLES (usually 128) + // float32_t. This buffer is within the audio library memory pool. + // Only a single buffer is allocated at any one time: repeated calls + // to getBuffer() without calling playBuffer() will yield the same address. + float32_t * getBuffer(void); + uint32_t playBuffer(void); + void stop(void); + void setMaxBuffers(uint8_t); + virtual void update(void); + enum behaviour_e {ORIGINAL,NON_STALLING}; + void setBehaviour(behaviour_e behave) + { + behaviour = behave; + } private: - audio_block_f32_t *queue[32]; - audio_block_f32_t *userblock; - volatile uint8_t head, tail; + audio_block_f32_t *queue[MAX_BUFFERS]; + audio_block_f32_t *userblock; + unsigned int uptr; // actually an index, NOT a pointer! + volatile uint8_t head, tail; + volatile uint8_t max_buffers; + behaviour_e behaviour; }; - #endif