Sunday, December 4, 2016

Extending Teensy Audio Library for Floating-Point

For my Teensy-based hearing aid, it will be much easier to program new audio processing algorithms if I can use floating-point aido data instead of fixed-point audio data.  Unfortunately, the Teensy Audio Library assumes the use fixed-point data types (ie, Int16) for all of its processing blocks.  So, I decided to extend that library to enable the floating-point processing that I want.  Here's an overview of how I made it work.  My "OpenAudio" library is available on my GitHub here.  I'd love any feedback (or GitHub pull requests!) on how it could be done better.
Teensy Audio Library Assumes Int16:  My goal has been to maintain as much of the Teensy Audio Library's structure as possible.  But, many of the core elements of the Teensy Audio Library are deeply entwined with the assumption of fixed-point Int16 audio data.  Shown in red in the figure above are several of the foundational elements of the Teensy Audio Library that are tied to Int16 audio data.  These are the elements that I extended.

OpenAudio F32 Versions:  To enable Float32 operations, I wrote new Float32 versions of these elements.  I used inheritance where possible to reduce the duplication of functionality, particularly for the AudioStream to AudioStream_F32 conversion.  To maintain the structure and conventions of the Teensy Audio Library, I made a one-for-one replacement so that one only need to substitute the "_F32" version for the standard version.

Conversion Routines:  To interface between the Int16 data of the Teensy Audio Library and the Float32 data used my extended library, I also wrote two new "AudioConvert" classes.  In addition to converting between the Int16 and Float32 data types, these routines also re-scale the data.  The Teensy Audio Library assumes "full scale" is ±32768 whereas my floating-point objects assume that ±1.0 is full scale.  My conversion routines automatically account for this difference.

OpenAudio F32 Example:  If you download the library and unzip it into your Arduino Libraries directory (see "Installation" notes here), you can start the Arduino IDE and load an example sketch that comes with the library.  Go under the "File" menu and select "Examples", then, "OpenAudio_ArduinoLibrary", then "BasicGain_Float".

Add a New #include:  Inside this sketch, you'll see many features that are similar to every sketch that uses the Teensy Audio Library.  For example, the screenshot below shows the #include statements at the beginning of the sketch.  Note that I added an #include for the new OpenAudio library.  Once this line is added, any of the new OpenAudio classes can be used.

Instantiate the New Classes:  In a typical sketch using the Teensy Audio Library, the next block of code instantiates the audio-related objects.  The screenshot below illustrates this by invoking the standard AudioControlSGTL5000, AudioInputI2S, and AudioOutputI2S classes.   After these standard lines, I added three new lines that instantiate blocks for floating-point processing.

In this case, AudioConvert_I16toF32 will convert the standard Int16 audio data to float32.  AudioEffectGain_F32 will apply gain to the float32 audio data.  AudioConvert_F32toI16 will convert the audio data back to Int16 so that it can be output by the usual functions of the Teensy Audio Library.

New Audio Connections:  After instantiating the objects, the next step is to make the audio connections between the objects.  The screenshot below shows that a mix of standard (Int16) connections and new (Float32) connections are used to make the full processing chain.  The standard connections (AudioConnection) are used whenever the data being passed is Int16 data.  The new connections (AudioConnection_F32) are used whenever the data is float32 data.

Allocate Float32 Memory:  The last step is shown in the screenshot below.  In the setup() function is where memory is allocated for the Teensy Audio Library via the AudioMemory() statement.  Following this same pattern, I added the AudioMemory_F32() statement to allocate the float32 memory that is needed for the floating-point processing.

Complie and Run:  If you have a Teensy 3.5 or 3.6 and a Teensy Audio Board, you can compile and run this example sketch.  If you have a potentiometer attached to the Teensy Audio Board's volume control, you can use the pot to adjust the volume of the sound (well, it actually adjusts the gain applied by the new floating-point gain block).  On my hardware, it works great!  Hopefully it works well on yours, too.

Next Steps:  With the floating-point audio processing structure in-place, I can move forward with adding more "F32" processing blocks for the different functions that I need.  My next block will be a dynamic range compressor.  Then I'll probably add a filtering block.  Eventually, I'll be looking to add frequency-domain processing, which will likely involve extending my library again for a "complex_float32" data type.  That'll be even more fun!

Update:  I had my first Pull Request with a user contribution.  So awesome!
Update:  I've added an algorithms: a basic Dynamic Range Compressor.