You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
OpenAudio_ArduinoLibrary/examples/FineFreqShift_OA/FineFreqShift_OA.ino

290 lines
11 KiB

/* FineFreqShift_OA.ino
*
* 1 Hz Resolution Frequency Shift and Synthetic Stereo
*
* Written for Open Audio Bob Larkin June 2020
*
* This sketch demonstrates the use of dual multipliers along with broad-band 90-degree
* phase shifters (Hilbert transforms) and sine/cosine generator to shift all frequencies
* by a fixed amount. The shift amount is set by a digital oscillator frequency
* and can be made arbitrarily precise. Thus the name "Fine Frequency Shifter."
*
* Two blocks do most of the work of shifting the frequency band. The RadioIQMixer_F32
* has a pair of multipliers that have one of their inputs from an oscillator that produces
* a sine and a cosine waveform of the same frequency. This oscillator phase difference
* is transferred to the output producing phase differences of 90-degrees. The multipliers,
* called double-balanced mixers in the analog world, produce sum and difference frequencies
* but the original input frequencies are suppressed. In the analog case, this suppression
* is limited by circuit balance, but for digital multipliers, the suppression is
* close-to-perfect. Thus the outputs of the I-Q Mixers is a pair of frequency sum and
* difference signals. Since the oscillator can be a low frequency, such as 100 Hz,
* the frequency band of sum and difference signals will include much overlap.
*
* The next block, the "AudioFilter90Deg_F32" applies a constant phase difference between
* the two inputs, which are then added or subtracted. That turns out to cause either the
* frequency sum or difference to be cancelled out. At that point only one audio signal
* remains and it is frequency shifted by the oscillator frequency.
*
* This INO also demonstrates the conversion of monaural to stereo sound by
* delaying one channel. This technique has been used for many years to de-correlate
* monaural noise before sending it to both ears. This can engage the the human brain
* to hear correlated signals better. It also adds a stereo presence to monaural voice
* that is pleasant to hear. It is tossed in here for experimentation. In general,
* delays up to 20 msec give the illusion of presence, whereas larger delays start to
* sound like loud echos! Play with it. Headphones vs. speakers change the perception.
*
* Control is done over the "USB Serial" using the Serial Monitor of the Arduino
* IDE. The commands control most functions and a list can be seen by typing
* "h<enter>", or looking in the listing below, at printHelp().
*
* Refs - The phasing method of SSB generation goes way back.
* https://en.wikipedia.org/wiki/Single-sideband_modulation#Hartley_modulator
* The precision of DSP makes it practical for overlapping input and output bands.
* I first encountered this in conversation with friend Johan Forrer:
* https://www.arrl.org/files/file/Technology/tis/info/pdf/9609x008.pdf
* I need to find the German description of delay stereo from the 1980's. Both audio
* shifting and delay stereo were put into the DSP-10 audio processor:
* http://www.janbob.com/electron/dsp10/dsp10.htm
*
* Tested OK for Teensy 3.6 and 4.0.
* For settings:
* sample rate (Hz) = 44117.00
* block size (samples) = 128
* N_FFT = 512
* Hilbert 251 taps
* CPU Cur/Peak, Teensy 3.6: 27.28%/27.49%
* CPU Cur/Peak, Teensy 4.0: 5.82%/5.84%
* Memory useage is 4 for I16 Memory
* Memory for F32 is 6 plus 1 more for every 2.9 mSec of Stereo Delay.
*
* This INO sketch is in the public domain.
*/
#include "Audio.h"
#include "AudioStream_F32.h"
#include "OpenAudio_ArduinoLibrary.h"
//set the sample rate and block size
const float sample_rate_Hz = 44117.f;
const int audio_block_samples = 128;
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);
//create audio library objects for handling the audio
AudioInputI2S i2sIn; // This I16 input/output is T4.x compatible
AudioConvert_I16toF32 cnvrt1; // Convert to float
RadioIQMixer_F32 iqmixer1;
AudioFilter90Deg_F32 hilbert1;
AudioMixer4_F32 sum1; // Summing node for the SSB receiver
AudioFilterFIR_F32 fir1; // Low Pass Filter to frequency limit the SSB
AudioEffectDelay_OA_F32 delay1; // Pleasant and useful sound between L & R
AudioConvert_F32toI16 cnvrt2;
AudioConvert_F32toI16 cnvrt3;
AudioOutputI2S i2sOut;
AudioControlSGTL5000 codec;
//Make all of the audio connections
AudioConnection patchCord0(i2sIn, 0, cnvrt1, 0); // connect to Left codec, 16-bit
AudioConnection_F32 patchCord1(cnvrt1, 0, iqmixer1, 0); // Input to 2 mixers
AudioConnection_F32 patchCord2(cnvrt1, 0, iqmixer1, 1);
AudioConnection_F32 patchCord3(iqmixer1, 0, hilbert1, 0); // Broadband 90 deg phase
AudioConnection_F32 patchCord4(iqmixer1, 1, hilbert1, 1);
AudioConnection_F32 patchCord5(hilbert1, 0, sum1, 0); // Sideband select
AudioConnection_F32 patchCord6(hilbert1, 1, sum1, 1);
AudioConnection_F32 patchCord7(sum1, 0, delay1, 0); // delay channel 0
AudioConnection_F32 patchCord9(sum1, 0, cnvrt2, 0); // connect to the left output
AudioConnection_F32 patchCordA(delay1, 0, cnvrt3, 0); // right output
AudioConnection patchCordB(cnvrt2, 0, i2sOut, 0);
AudioConnection patchCordC(cnvrt3, 0, i2sOut, 1);
//control display and serial interaction
bool enable_printCPUandMemory = false;
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; };
// Filter for AudioFilter90Deg_F32 hilbert1
#include "hilbert251A.h"
//inputs and levels
float gain_dB = -15.0f;
float gain = 0.177828f; // Same as -15 dB
float sign = 1.0f;
float deltaGain_dB = 2.5f;
float frequencyLO = 100.0f;
float delayms = 1.0f;
// *************** SETUP **********************************
void setup() {
Serial.begin(1); delay(1000);
Serial.println("*** Fine Frequency Shifter - June 2020 ***");
Serial.print("Sample Rate in Hz = ");
Serial.println(audio_settings.sample_rate_Hz, 0);
Serial.print("Block size, samples = ");
Serial.println(audio_settings.audio_block_samples);
AudioMemory(10); // I16 type
AudioMemory_F32(200, audio_settings);
//Enable the codec to start the audio flowing!
codec.enable();
codec.adcHighPassFilterEnable();
codec.inputSelect(AUDIO_INPUT_LINEIN);
iqmixer1.frequency(frequencyLO); // Frequency shift, Hz
deltaFrequency(0.0f); // Print freq
hilbert1.begin(hilbert251A, 251); // Set the Hilbert transform FIR filter
sum1.gain(0, gain*sign); // Set gains
sum1.gain(1, gain);
delay1.delay(0, delayms); // Delay right channel
deltaDelay(0.0f); // Print delay
//finish the setup by printing the help menu to the serial connections
printHelp();
}
// ************************* LOOP ****************************
void loop() {
//respond to Serial commands
while (Serial.available()) respondToByte((char)Serial.read());
//check to see whether to print the CPU and Memory Usage
if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec
} //end loop();
//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) {
//static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
static unsigned long lastUpdate_millis = 0;
//has enough time passed to update everything?
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
Serial.print("CPU Cur/Peak: ");
Serial.print(audio_settings.processorUsage());
Serial.print("%/");
Serial.print(audio_settings.processorUsageMax());
Serial.println("%");
Serial.print(" Audio MEM Float32 Cur/Peak: ");
Serial.print(AudioMemoryUsage_F32());
Serial.print("/");
Serial.println(AudioMemoryUsageMax_F32());
Serial.print(" Audio MEM Int16 Cur/Peak: ");
Serial.print(AudioMemoryUsage());
Serial.print("/");
Serial.println(AudioMemoryUsageMax());
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
}
}
void incrementGain(float increment_dB) {
gain_dB += increment_dB;
// gain is set in the "mixer" block, including sign for raise/lower freq
gain = powf(10.0f, 0.05f * gain_dB);
sum1.gain(0, gain*sign);
sum1.gain(1, gain);
printGainSettings();
}
void printGainSettings(void) {
Serial.print("Gain in dB = "); Serial.println(gain_dB, 1);
}
void deltaFrequency(float dfr) {
frequencyLO += dfr;
Serial.print("Frequency shift in Hz = ");
Serial.println(frequencyLO, 1);
if(frequencyLO < 0.0f)
sign = 1.0f;
else
sign = -1.0f;
iqmixer1.frequency(fabsf(frequencyLO));
incrementGain(0.0f);
}
void deltaDelay(float dtau) {
delayms += dtau;
if (delayms < 0.0f)
delayms = 0.0f;
delay1.delay(0, delayms); // Delay right channel
Serial.print("Delay in milliseconds = ");
Serial.println(delayms, 1);
}
//switch yard to determine the desired action
void respondToByte(char c) {
char s[2];
s[0] = c; s[1] = 0;
if( !isalpha((int)c) && c!='?') return;
switch (c) {
case 'h': case '?':
printHelp();
break;
case 'g': case 'G':
printGainSettings();
break;
case 'k':
incrementGain(deltaGain_dB);
break;
case 'K': // which is "shift k"
incrementGain(-deltaGain_dB);
break;
case 'C': case 'c':
Serial.println("Toggle printing of memory and CPU usage.");
togglePrintMemoryAndCPU();
break;
case 'd':
deltaFrequency(1.0f);
break;
case 'D':
deltaFrequency(-1.0f);
break;
case 'e':
deltaFrequency(10.0f);
break;
case 'E':
deltaFrequency(-10.0f);
break;
case 'f':
deltaFrequency(100.0f);
break;
case 'F':
deltaFrequency(-100.0f);
break;
case 't':
deltaDelay(1.0f);
break;
case 'T':
deltaDelay(-1.0f);
break;
case 'u':
deltaDelay(10.0f);
break;
case 'U':
deltaDelay(-10.0f);
break;
default:
Serial.print("You typed "); Serial.print(s);
Serial.println(". What command?");
}
}
void printHelp(void) {
Serial.println();
Serial.println("Help: Available Commands:");
Serial.println(" h: Print this help");
Serial.println(" g: Print the gain settings of the device.");
Serial.println(" C: Toggle printing of CPU and Memory usage");
Serial.print( " k: Increase the gain of both channels by ");
Serial.print(deltaGain_dB); Serial.println(" dB");
Serial.print( " K: Decrease the gain of both channels by ");
Serial.print(-deltaGain_dB); Serial.println(" dB");
Serial.println(" d: Raise frequency by 1 Hz");
Serial.println(" D: Lower frequency by 1 Hz");
Serial.println(" e: Raise frequency by 10 Hz");
Serial.println(" E: Lower frequency by 10 Hz");
Serial.println(" f: Raise frequency by 100 Hz");
Serial.println(" F: Lower frequency by 100 Hz");
Serial.println(" t: Raise stereo delay by 1 msec");
Serial.println(" T: Lower stereo delay by 1 msec");
Serial.println(" u: Raise stereo delay by 10 msec");
Serial.println(" U: Lower stereo delay by 10 msec");
}