Floating-Point Audio Library Extension

Hi Frank - I think the idea of the Charlie Hill FT-8 communicator is fun. I know he operated from Hyde Park in London with success. Be aware that his code adds powers in dB, as I recollect, and it should be in pure power. Also, I don't think he did an estimate of snr. But those are details. The big thing is that he got it working. Also, I have little interest in doing FT8 for collecting countries or something. But I do like the idea of being able to send text messages via FT8 while away from civilized cell coverage. The software supports a random message, but the user interface on a 240x320 screen is challenging. It must be possible. I bet you could do it! Bob
 
Hi Bob, thanks for the motivation! :) will try my very best, although time is quite limited at the moment. Yes, I totally agree, FT8 for emergency communication seems much more useful and interesting than semiautomatic collection of countries...
Seems my message in the KeithSDR group was forgotten or not positively approved, so I will quit that group two weeks after I joined it.
At the moment I am slowly ⁰building the hardware for the Charlie Hill FT8 communicator on the experimental PCB you can purchase together with the QCX by QRP labs. This enables me to first build the original design and then add the QCX Rx QSD and Tx class E PA underneath to extend it to a stand-alone Teensy FT8 Transceiver. I plan to use Teensy 4.1 and an ADC PCM1808 after the QSD. In the software it should be easy to add two of your OpenAudio lib classes to make the receive branch work (in theory :). Will open a separate thread for this as soon as I make some progress.
Best regards Frank DD4WH
 
I added another class to the OpenAudio_ArduinoLibrary to transmit Morse code. This is intended primarily for amateur radio. Called radioCWModulator_F32 it receives ASCII data into a 512 character circular buffer and makes the resulting audio available as an output. Most punctuation and amateur radio CW symbols are included. Gaussian bandwidth shaping is used and the output is a keyed sine wave of programmable frequency and amplitude. The code speed can be varied from 5 to 50 words per minute and the weighting of the code varies with speed. For more information, look at the Design tool for the floating point library. The source is at Github with more information available in the radioCWModulator_F32.h file.

There is no corresponding "receive CW" class. I have no plans to add that in the near future, but if someone wants to write it, I would be happy to support the effort.
 
Cool that the CESSB functionality has been included to the lib. If the "IF" out is around 10 KHz, how do I mix this up if I want to use it as a SSB transmitter? I guess I would need very sharp filters to get rid of the image product only 10 kHz away.

Alternatively I guess one could use the I and Q outputs but I am looking for the least complicated way of making a CESSB HF transmitter to test.

@boblarkin
 
Hi Mike - There are two ways to get to HF. They both have pros and cons.

You can use radioCESSB_Z_transmit_F32 and you get a baseband SSB, either USB or LSB, ready to modulate an IQ mixer pair. No sharp filtering is needed.

Or, you can use the radioCESSBtransmit_F32 and it has the Weaver generated pair of signals that can be used to generate an I-F signal, at say 10 kHz. To get this to, say 14 MHz, requires an intermediate filter, such as a crystal filter.

Both work and generate a really nice CESSB result, due to the work of Dave Hershberger!

Let us know a bit more about your end goals and that helps pick the best method. Also, I assume you looked at the two example INO's that might help.

Bob W7PUA
 
Hello Bob. The plan is to make a standalone cessb exciter. Is there a IQ mixer design with si5351 clock generatotor I could take a look at somewhere?
 
Hello experts. Is there a working audio compressor for voice use available for teensy now? Last time I looked (some years ago) I had to port some c code only with partly success.
 
Hi Mike - I am not current in what is available for IQ hardware. I know K7MDL has used the RS-HFIQ that may be out of stock. But the schematics are available and a they should tell how to build just the pieces that you need. There must be more approaches to this, but I know the RS-HFIQ was successful. Bob
 
Hi Mike - I am not current in what is available for IQ hardware. I know K7MDL has used the RS-HFIQ that may be out of stock. But the schematics are available and a they should tell how to build just the pieces that you need. There must be more approaches to this, but I know the RS-HFIQ was successful. Bob

I found this schematic (see page 4). https://ad5gh.files.wordpress.com/2023/02/qsd-qse-lo-board_v3.pdf
I see there are many implementations of the QSE. Some use transformer on the output, some uses no transformer. There are also several types of OP amps used. I wonder what is the most optimal design.
 
I wonder what is the most optimal design.
I suspect, for transmitting, the criteria for optimal should be carrier rejection. That is because most of the other problems can be dealt with in DSP software. But, the carrier balance comes from the hardware alone. That may not be helpful, as in my limited looking, I have not seen comparisons. But, most designs will have 40+ dB carrier suppression which is certainly adequate for low power experiments.

The mixers and drivers in your referenced schematic seems fine. Can you buy a board for that and populate the parts that you need?
 
How can I add the Open Audio library when I use PlatformIO and not the Arduino IDE? Is it poassible to include a link to the github repository?
 
I have no experience with PlatformIO. But, git is an easy way to keep your external library up-to-date, independent of what IDE you are using. The internet is loaded with examples of this. Once you have your favorite few git commands it can be very smooth.

I might mention, though, that if you modify your local files at all, be ready for some complaints from git. Just updating to Github does not have this.
 
I have the Floating point audio library running on my Teensy 3.6. After some USB trouble where I used the USBDeview tool to delete old USB info in Windows, I got audio from a mic connected to the SGTL5000 thru to USB and Audacity on my Windows 10 PC. However I have problems with distorted audio. It almost seems like there is a buffer overrun problem or timing problem. When I use the old integer based audio library I manage to get clean audio thru. This leads me to think that I may do something wrong when using the Floating point audio library

This is my test code. Can you spot problems? Anyone care to test on your setup?

Code:
#include <Arduino.h>


#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <USB_Audio_F32.h>


// GUItool: begin automatically generated code
AudioInputI2S_F32            audioInI2S1;    
AudioOutputUSB_F32           audioOutUSB1; 
AudioConnection_F32          patchCord1(audioInI2S1, 0, audioOutUSB1, 0);
AudioConnection_F32          patchCord2(audioInI2S1, 1, audioOutUSB1, 1);
AudioControlSGTL5000         sgtl5000_1;    
// GUItool: end automatically generated code


const float sample_rate_Hz = 44100.0f;

const int   audio_block_samples = 128;  // Always 128

AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);

void setup() {

  AudioMemory(20);
  AudioMemory_F32(50, audio_settings);

  sgtl5000_1.enable();
 
  sgtl5000_1.inputSelect(AUDIO_INPUT_MIC);

  delay(50);

  Serial.begin(9600);
  delay(50);
 
  
}

void loop() {
  Serial.println(AUDIO_SAMPLE_RATE_EXACT);
  delay(50);
}
 
Hi Mike - I've been away and fallen behind. A couple of thoughts, though, on mixed USB / Teensy Codec audio. As I understand it, the USB audio must come with an implied sample rate. This might be 44100 sps. The Codec has a sample rate set (usually) by the Teensy clock. This might be 44110 sps due to clock errors. This difference in sample rates needs to be resolved or the USB audio, sampled at the Codec rate will have extra/missing samples.

I have not been involved with the USB I/O so the details are out of my range. Off hand, doing a really good job of resolving the extra/missing sample issue could be challenging. Maybe someone more familiar with the USB I/O could comment?? I Bob
 
Hi everyone, I am trying to use the OpenAudio_ArduinoLibrary on a teensy4.1 to record sound on the SD card in 32bits in the same way as the "Recorder" example in the usual Audio library. However, I can't seem to get it to work right when playing the audio on PC.
Here is my code :
Code:
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include "Arduino.h"
#include "play_queue_f32.h"
#include <Wire.h>
#include <SD.h>

// GUItool: begin automatically generated code
AudioInputI2S_F32            i2s1;           //xy=231.25,278.25
AudioRecordQueue_F32         queue1;         //xy=471.25,262.25
AudioConnection_F32          patchCord1(i2s1, 0, queue1, 0);
// GUItool: end automatically generated code


FsFile file1;

void setup() {
  AudioMemory(6);
  AudioMemory_F32(200); 
  delay(10);

  Serial.begin(115200);
  delay(10);

  bool ok;
  Serial.print("Initializing SD card...");
  ok = SD.sdfs.begin(SdioConfig(DMA_SDIO)); // sdfat library
  if (!ok) {
      Serial.println("initialization failed!");
      while (1);
  }
  Serial.println("initialization done.");
  Serial.println();
  if (SD.exists("test.raw")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("test.raw");
  }

  file1 = SD.sdfs.open("test.raw", FILE_WRITE | O_CREAT);
  delay(10);
  Serial.println("test.raw opened");
  Serial.println();

 queue1.begin();

  
}

void loop() {
  // put your main code here, to run repeatedly:
    if (queue1.available() >= 16) {


    byte buffer1[2048];
    // Fetch 2 blocks from the audio library and copy
    // into a 2048 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.

    for(int i=0; i<16; i++){
      memcpy(buffer1+(i*128), queue1.readBuffer(), 128);
      queue1.freeBuffer();
    }

    //Serial.println();
    // print max audio memory AudioMemoryUsageMax();
    //Serial.print("AudioMemoryUsageMax32 = ");
    //Serial.println(AudioMemoryUsageMax_F32());
    //Serial.print("AudioMemoryUsageMax = ");
    //Serial.println(AudioMemoryUsageMax());

    file1.write(buffer1, 2048);
    file1.sync();

  }
  delay(10);
}

I tried changing the size of the buffer and changing or removing the delay in each loop with no success.
I use this command to convert the file :
Code:
sox -t raw -b 32 -e floating-point -r 44100 -c 1 test.raw output.wav
Can you please pinpoint what is wrong with my code?
 
Last edited:
I have not attempted to run your sketch, but a couple of things: You do not have a control object, such as SGL5000 for the Teensy audio adapter. Also, there seems to be a data type issue. The AudioRecordQueue_F32 will produce an array of 128 float (aka float32_t). Each float is 4 bytes. I believe memcpy just deals with bytes. Each call to queue1.readBuffer() produces 128 floats. 16 of these calls produces 16*128 floats that occupies 8Kbytes. If I followed that correctly! Bob
 
Hi Bob, thank you for you response. I am not using a control object because I am using an ADC (the TLV320ADC6120). I tried fixing the data type issue and it worked ! here is the complete code if anyone is interested :
Code:
#include "OpenAudio_ArduinoLibrary.h"
#include <Wire.h>
//#include <SPI.h>
#include <SD.h>
//#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S_F32            i2s1;           //xy=231.25,278.25
AudioRecordQueue_F32         queue1;         //xy=471.25,262.25
AudioRecordQueue_F32         queue2;         //xy=483.25,306.25
AudioConnection_F32          patchCord1(i2s1, 0, queue1, 0);
AudioConnection_F32          patchCord2(i2s1, 1, queue2, 0);
// GUItool: end automatically generated code


FsFile file1;
FsFile file2;

void setup() {
  AudioMemory(6);
  AudioMemory_F32(200);
  delay(10);
  Wire2.begin();
  delay(500);

// Wake-up output block (default state after powering)
// The first line sends the addressing packet "0x9C" (see TLV320ADC6120 datasheet)
// The address is indeed 0x4E in 7 bits, the write bit transforms it to 0x9C
Wire2.beginTransmission(0x4E);
Wire2.write(0x02);
Wire2.write(0x81);
Wire2.endTransmission();
delay(300); // Allows board to wake up

// Board configuration
// I2S output configuration
Wire2.beginTransmission(0x4E);
Wire2.write(0x07);
Wire2.write(0x38);
Wire2.endTransmission();
delay(300);

// Enable inputs 1 and 2
Wire2.beginTransmission(0x4E);
Wire2.write(0x73);
Wire2.write(0xC0);
Wire2.endTransmission();
delay(300);

// Enable audio output
Wire2.beginTransmission(0x4E);
Wire2.write(0x74);
Wire2.write(0xC0);
Wire2.endTransmission();
delay(300);

// Power up the microphones, internal ADC, and PLL
Wire2.beginTransmission(0x4E);
Wire2.write(0x75);
Wire2.write(0xE0);
Wire2.endTransmission();
delay(300);

// Apply gain of 1dB to ch1
Wire2.beginTransmission(0x4E);
Wire2.write(0x3D);
Wire2.write(0x80);
Wire2.endTransmission();
delay(300);
// Apply gain of 1dB to ch2
Wire2.beginTransmission(0x4E);
Wire2.write(0x42);
Wire2.write(0x80);
Wire2.endTransmission();
delay(300);

Serial.begin(115200);
delay(10);
// Here, we are monitoring the I2S input and have sound.
// Currently set to 44.1kHz and 32-bit precision

// BCLK 6.144MHz for 96kHz 32-bit, studio quality

  bool ok;
  Serial.print("Initializing SD card...");
  ok = SD.sdfs.begin(SdioConfig(DMA_SDIO)); // sdfat library
  if (!ok) {
      Serial.println("initialization failed!");
      while (1);
  }
  Serial.println("initialization done.");
  Serial.println();
  if (SD.exists("test_1.raw")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("test_1.raw");
  }
  if (SD.exists("test_2.raw")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("test_2.raw");
  }
  file1 = SD.sdfs.open("test_1.raw", FILE_WRITE | O_CREAT);
  file2 = SD.sdfs.open("test_2.raw", FILE_WRITE | O_CREAT);
  delay(10);
  Serial.println("test.raw opened");
  Serial.println();

 queue1.begin();
 queue2.begin();

  
}

void loop() {
  // put your main code here, to run repeatedly:
    if (queue1.available() >= 16) {


byte buffer1[8192]; // Increase buffer size to accommodate 8Kbytes (16*128*4)
float* floatBuffer = (float*)buffer1; // Treat buffer1 as float array

for (int i = 0; i < 16; i++) {
    float* readBuffer = queue1.readBuffer(); // Assume queue1.readBuffer() returns float*
    memcpy(floatBuffer + (i * 128), readBuffer, 128 * sizeof(float));
    queue1.freeBuffer(); // Corrected the queue number to 1
}
    //Serial.println("done4");
    // write all 512 bytes to the SD card
    //elapsedMicros usec = 0;
    //Serial.println();
    // print max audio memory AudioMemoryUsageMax();
    // Serial.print("AudioMemoryUsageMax32 = ");
    // Serial.println(AudioMemoryUsageMax_F32());
    // Serial.print("AudioMemoryUsageMax = ");
    // Serial.println(AudioMemoryUsageMax());

    file1.write(buffer1, 8192);
    file1.sync();
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
  }
    if (queue2.available() >= 16) {


    byte buffer2[8192]; // Increase buffer size to accommodate 8Kbytes (16*128*4)
    float* floatBuffer = (float*)buffer2; // Treat buffer2 as float array

    for (int i = 0; i < 16; i++) {
        float* readBuffer = queue2.readBuffer(); // Assume queue1.readBuffer() returns float*
        memcpy(floatBuffer + (i * 128), readBuffer, 128 * sizeof(float));
        queue2.freeBuffer(); // Corrected the queue number to 1
    }
    // write all 512 bytes to the SD card
    //elapsedMicros usec = 0;
    file2.write(buffer2, 8192);
    file2.sync();
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The queue1 object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //Serial.print("SD write, us=");
    //Serial.println(usec);
  }
  delay(5);
}
 
Last edited:
I get pops or distorted USB audio when I change my SDR code audio sample rate and the PC side detected sample rate and (settings in the device advanced properties) are not set to match. In my case I modified the TeensyDuino library to run the USB audio at 48Khz for my needs. Transmit audio to the SDR was always off. Fought with that for some time and finally got both directions of USB audio working at 48KHz. If I change the SDR audio sample rate, say to 44.1 or 22KHz, it distorts the audio, they had to be the same for me. The more you change the rate, the worse the rate until you finally do not hear much audio. Instructions on how I changed mine are on my SDR Github site. The biggest tell is the USB DevView reported sample rate value.

- Mike K7MDL
 
Thanks again, Bob for your efforts on FT8 Rx! I have now implemented it into Teensy Convolution SDR and it works very well:
https://youtu.be/nKm-cLcOV94
Time can be synchronized by turning the encoder while in Time Set menu. This is totally sufficient, no need for GPS or internet server time synchronization.
Will issue the code on github in the next days.
The T4.1 is at its limits regarding memory and speed.
Thanks again for providing this marvelous playground!!!
 
Back
Top