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/radioCWModulator_F32.h

357 lines
14 KiB

/*
* radioCWModulator_F32.h For the OpenAudio_TeensyArduino library
* (floating point audio).
* MIT License - Copyright March 2023 Bob Larkin, original write
*
* Parts of this are based on Audio Library for Teensy 3.x and 4.x that is
* Copyright (c) 2023, Paul Stoffregen, paul@pjrc.com
*
* Development of the Teensy 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.
*/
/* The method comes directly from the UHFA program for the DSP-10 project of 1999
* by Bob Larkin. Much of the code comes from there, as well.
*
* Sample rates: The CW is generated at a sample rate of 12 ksps. This
* supports a good Gaussian filter to limit the spectral width which is
* equivalent to removing "key clicks." To use higher sample rates, the
* generation is only done for every 2nd, 4th or 8th sample. The output
* for sample rates above 12 ksps are interpolated. The supported sample
* rates become 11.025, 12, 44.1, 48, 50, 88, 96 and 100 ksps or others
* in those general ranges.
*/
#ifndef radioCWModulator_F32_h_
#define radioCWModulator_F32_h_
#include "Arduino.h"
#include "AudioStream_F32.h"
class radioCWModulator_F32 : public AudioStream_F32
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName:DetCTCSS
public:
radioCWModulator_F32(void) : AudioStream_F32(0, NULL) {
sample_rate_Hz = AUDIO_SAMPLE_RATE;
block_size = AUDIO_BLOCK_SAMPLES;
initCW();
}
// Option of AudioSettings_F32 change sample rate (always 128 block size):
radioCWModulator_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
sample_rate_Hz = settings.sample_rate_Hz;
block_size = AUDIO_BLOCK_SAMPLES;
initCW();
}
// Values for stateCW
#define IDLE_CW 0
#define DASH_CW 1
#define DOT_CW 2
#define DOT_DASH_SPACE 3
#define CHAR_SPACE 4
#define WORD_SPACE 5
#define LONG_DASH 6
void initCW(void) { // Public, but primarily for building objects. Not required in INO.
for(int kk=0; kk<512; kk++)
sendBuffer[kk] = 0;
indexW = 0;
indexR = 0;
setCWSpeedWPM(speedWPM);
arm_fir_init_f32(&interpolateLPFInst, 59, &interpolateLPF[0], &interpolateLPFState[0], 128);
setSampleRate_Hz(sample_rate_Hz);
setFrequency(600.0f);
}
// Enables the operation of the update(). This is needed when there
// are separate transmit and receive times.
void enableTransmit(bool _transmit) {
enableXmit = _transmit;
}
bool getEnableTransmit(void) {
return enableXmit;
}
uint16_t getBufferSpace(void) {
return 512 - ((indexW - indexR) & 0X1FF);
}
// Place character into queue for sending
bool sendCW(uint16_t _ch) {
if(getBufferSpace() > 0)
{
sendBuffer[indexW++] = _ch;
indexW &= 0X1FF;
return true;
}
else
return false;
}
bool sendStringCW(char* _pStr)
{
uint16_t space = getBufferSpace();
uint16_t size = strlen(_pStr);
// Serial.print(space); Serial.print(" space size "); Serial.println(size);
if(space < size) return false;
for(int kk=0; kk<(int)strlen(_pStr); kk++)
sendCW( (uint16_t)*(_pStr+kk) );
return true;
}
void setCWSpeedWPM(uint16_t _speed) {
uint16_t kk = 0;
while(tCW[kk].speedWPM < _speed)
kk++;
speedIndex = kk;
dotCW = (uint32_t)tCW[kk].dot; /* msec per dot */
dashCW = (uint32_t)tCW[kk].dash; /* msec per dash */
ddCW = (uint32_t)tCW[kk].dd; /* msec between dot or dash */
chCW = (uint32_t)tCW[kk].ch; /* msec at end of each char */
spCW = (uint32_t)tCW[kk].sp; /* msec for space char */
}
void setFrequency(float32_t _freq) { // Defaults to 600 Hz
frequencyHz = _freq;
if (frequencyHz < 0.0f)
frequencyHz = 0.0f;
if (frequencyHz > sample_rate_Hz/2.0f)
frequencyHz = sample_rate_Hz/2.0f;
phaseIncrement = 512.0f * frequencyHz / sample_rate_Hz;
}
void setLongDashMs(uint32_t _longDashMs) { // Defaults to 1000 mSec
longDashCW = _longDashMs;
}
// See note above on sample rates.
void setSampleRate_Hz (float32_t fs_Hz) { // (const float32_t &fs_Hz) {
sample_rate_Hz = fs_Hz;
timeSamplesMs = 1000.0f/sample_rate_Hz; // In millisec
if(sample_rate_Hz>11000.0f && sample_rate_Hz<12100.0f)
{
nSample = 1;
nSamplesPerUpdate = 128;
}
else if(sample_rate_Hz>44000.0f && sample_rate_Hz<50100.0f)
{
nSample = 4;
nSamplesPerUpdate = 32;
}
else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f)
{
nSample = 8;
nSamplesPerUpdate = 16;
}
else
{
Serial.print(sample_rate_Hz);
Serial.println(" sample rate not supported.");
nSample = 4;
nSamplesPerUpdate = 32;
}
arm_fir_init_f32(&GaussLPFInst, 145, &firGaussCoeffs[0], &GaussState[0], nSamplesPerUpdate);
setFrequency(frequencyHz);
}
// The amplitude, a, is the peak, as in zero-to-peak. This produces outputs
// ranging from -a to +a.
void amplitude(float32_t _a) {
if (_a < 0.0f)
_a = 0.0f;
magnitude = _a;
}
virtual void update(void);
private:
uint8_t sendBuffer[512]; // Circular send buffer
uint16_t indexW = 0; // Write index in to buffer
uint16_t indexR = 0; // Read index into buffer
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;
int16_t block_size = 128;
int16_t nSample = 4; //
int16_t nSamplesPerUpdate = 32;
uint16_t stateCW = IDLE_CW;
uint8_t c = 0;
uint16_t ic = 0; // Current character as integer
bool enableXmit = true;
float32_t levelCW = 0.0f; // sample by sample
float32_t frequencyHz = 600.0f;
float32_t phaseIncrement = 512.0f*frequencyHz/sample_rate_Hz;
float32_t phaseS = 0.0f;
float32_t magnitude = 1.0f;
float32_t timeSamplesMs = 1000.0f/sample_rate_Hz; // In millisec
float32_t timeMsF = 0.0f; // Update the event clock
uint32_t timeMsI = 0; // This is easier to use
uint16_t speedWPM = 13;
uint16_t speedIndex = 8;
uint32_t dotCW; /* msec per dot */
uint32_t dashCW; /* msec per dash */
uint32_t ddCW; /* msec between dot or dash */
uint32_t chCW; /* msec at end of each char */
uint32_t spCW; /* msec for space char */
uint32_t longDashCW = 1000;
float32_t* firGaussCoeffs = coeffLPFGaussCW70;
arm_fir_instance_f32 GaussLPFInst;
float32_t GaussState[128 + 145];
arm_fir_instance_f32 interpolateLPFInst;
float32_t interpolateLPFState[128 + 59];
/* The following mc[] table defines the morse code sequences
* in terms of bits. Starting from lsb and continuing until only one
* "1" remains, send a dot for a "0" and a dash for a "1", with
* a right shift after each dot or dash. */
/* sp " $ ' */
const uint16_t mc[64] = {0x17,0x01,0x52,0x01,0xC8,0x01,0x01,0x5E,
/* ( ) , - . / */
0x2D,0x6D,0x01,0x01,0x73,0x61,0x6A,0x29,
/* 0 1 2 3 4 5 6 7 */
0x3f,0x3e,0x3c,0x38,0x30,0x20,0x21,0x23,
/* ; = long dash 8 9 : ; = KN ? */
0x27,0x2f,0x47,0x01,0x01,0x31,0x2D,0x4C,
/* @ A B C D E F G */
0x5a,0x06,0x11,0x15,0x09,0x02,0x14,0x0b,
/* H I J K L M N O */
0x10,0x04,0x1e,0x0d,0x12,0x07,0x05,0x0f,
/* P Q R S T U V W */
0x16,0x1b,0x0a,0x08,0x03,0x0c,0x18,0x0e,
/* X Y Z AR AS SK Err _ */
0x19,0x1d,0x13,0x2A,0x22,0x68,0x80,0x6C};
/* The following set the weightings vs. code speed in wpm
* by selecting the delays for
* .dot length of a dot in ms
* .dash length of a dash in ms
* .dd time between dots and dashes, ms
* .ch time between characters of the same word, ms
* .sp time duration between words, ms */
struct cw_data {
uint16_t speedWPM;
uint16_t dot; /* msec per dot */
uint16_t dash; /* msec per dash */
uint16_t dd; /* msec between dot or dash */
uint16_t ch; /* msec at end of each char */
uint16_t sp; /* msec for space char */
};
/* Note: Code speed can be any. There are 5 times in milliseconds that
* set the actual speed and weighting. The index (0, 28) allows 29 of these
* that are initialized to 5 WPM to 50 WPM with nominal weightings. */
struct cw_data tCW[29] = {
{ 5, 150, 530, 150, 900,1200},
{ 6, 140, 480, 140, 750,1000},
{ 7, 125, 430, 125, 550, 800},
{ 8, 120, 400, 120, 400, 725},
{ 9, 120, 380, 120, 300, 560},
{ 10, 120, 360, 120, 240, 480},
{ 11, 110, 330, 110, 220, 440},
{ 12, 100, 300, 100, 200, 400},
{ 13, 92, 276, 92, 184, 368},
{ 14, 86, 258, 86, 172, 344},
{ 15, 80, 240, 80, 160, 320},
{ 16, 75, 225, 75, 150, 300},
{ 17, 70, 212, 70, 142, 283},
{ 18, 66, 200, 66, 134, 267},
{ 19, 63, 189, 63, 126, 252},
{ 20, 60, 180, 60, 120, 240},
{ 21, 57, 171, 57, 114, 230},
{ 22, 55, 164, 55, 109, 220},
{ 23, 52, 157, 52, 105, 210},
{ 24, 50, 150, 50, 100, 200},
{ 25, 48, 144, 48, 96, 192},
{ 26, 47, 138, 47, 91, 185},
{ 27, 45, 133, 45, 88, 179},
{ 28, 43, 129, 43, 86, 172},
{ 29, 41, 124, 41, 83, 166},
{ 30, 40, 120, 40, 80, 160},
{ 35, 34, 103, 34, 69, 138},
{ 40, 30, 90, 30, 60, 120},
{ 50, 24, 72, 24, 48, 96}};
// CW LPF Gaussian 12 ksps, 70 Hz at 3 dB
float32_t coeffLPFGaussCW70[145] = {
0.0000000994f, 0.0000002465f, 0.0000005437f, 0.0000010924f, 0.0000020348f,
0.0000035609f, 0.0000059142f, 0.0000093971f, 0.0000143746f, 0.0000212771f,
0.0000306017f, 0.0000429118f, 0.0000588363f, 0.0000790660f, 0.0001043500f,
0.0001354903f, 0.0001733351f, 0.0002187713f, 0.0002727164f, 0.0003361089f,
0.0004098986f, 0.0004950364f, 0.0005924635f, 0.0007031003f, 0.0008278357f,
0.0009675161f, 0.0011229345f, 0.0012948202f, 0.0014838290f, 0.0016905333f,
0.0019154139f, 0.0021588516f, 0.0024211200f, 0.0027023793f, 0.0030026708f,
0.0033219125f, 0.0036598959f, 0.0040162835f, 0.0043906075f, 0.0047822700f,
0.0051905436f, 0.0056145733f, 0.0060533799f, 0.0065058635f, 0.0069708087f,
0.0074468905f, 0.0079326809f, 0.0084266565f, 0.0089272066f, 0.0094326423f,
0.0099412055f, 0.0104510798f, 0.0109603999f, 0.0114672630f, 0.0119697396f,
0.0124658847f, 0.0129537493f, 0.0134313915f, 0.0138968878f, 0.0143483444f,
0.0147839080f, 0.0152017763f, 0.0156002085f, 0.0159775353f, 0.0163321678f,
0.0166626067f, 0.0169674505f, 0.0172454031f, 0.0174952809f, 0.0177160188f,
0.0179066758f, 0.0180664400f, 0.0181946320f, 0.0182907087f, 0.0183542653f,
0.0183850369f, 0.0183828991f, 0.0183478684f, 0.0182801008f, 0.0181798905f,
0.0180476675f, 0.0178839942f, 0.0176895623f, 0.0174651874f, 0.0172118049f,
0.0169304634f, 0.0166223192f, 0.0162886292f, 0.0159307439f, 0.0155500995f,
0.0151482106f, 0.0147266612f, 0.0142870968f, 0.0138312154f, 0.0133607587f,
0.0128775032f, 0.0123832512f, 0.0118798218f, 0.0113690419f, 0.0108527375f,
0.0103327249f, 0.0098108024f, 0.0092887417f, 0.0087682803f, 0.0082511135f,
0.0077388875f, 0.0072331919f, 0.0067355538f, 0.0062474314f, 0.0057702081f,
0.0053051882f, 0.0048535913f, 0.0044165491f, 0.0039951010f, 0.0035901916f,
0.0032026682f, 0.0028332785f, 0.0024826695f, 0.0021513865f, 0.0018398728f,
0.0015484699f, 0.0012774182f, 0.0010268582f, 0.0007968321f, 0.0005872861f,
0.0003980729f, 0.0002289548f, 0.0000796069f,-0.0000503789f,-0.0001614900f,
-0.0002542886f,-0.0003294068f,-0.0003875421f,-0.0004294517f,-0.0004559477f,
-0.0004678910f,-0.0004661861f,-0.0004517752f,-0.0004256325f,-0.0003887583f,
-0.0003421730f,-0.0002869118f,-0.0002240186f,-0.0001545404f,-0.0000795219f};
/* FIR filter designed with http://t-filter.appspot.com
Fs = 96000 Hz [48000]
0 Hz to 4000 Hz [0 to 2000] actual ripple = 0.05 dB
11000 Hz to 48000 Hz [5500 to 24000] actual atten = -93.1 dB */
float32_t interpolateLPF[59] = {
-0.000052355f,-0.000146720f,-0.000307316f,-0.000517033f,-0.000721194f,-0.000819999f,
-0.000680277f,-0.000169005f, 0.000793689f, 0.002172809f, 0.003767656f, 0.005194993f,
0.005927352f, 0.005395822f, 0.003149023f,-0.000960853f,-0.006611837f,-0.012919698f,
-0.018466308f,-0.021473003f,-0.020108212f,-0.012880252f, 0.000965492f, 0.021140621f,
0.046185831f, 0.073574790f, 0.100053429f, 0.122161788f, 0.136838160f, 0.141979757f,
0.136838160f, 0.122161788f, 0.100053429f, 0.073574790f, 0.046185831f, 0.021140621f,
0.000965492f,-0.012880252f,-0.020108212f,-0.021473003f,-0.018466308f,-0.012919698f,
-0.006611837f,-0.000960853f, 0.003149023f, 0.005395822f, 0.005927352f, 0.005194993f,
0.003767656f, 0.002172809f, 0.000793689f,-0.000169005f,-0.000680277f,-0.000819999f,
-0.000721194f,-0.000517033f,-0.000307316f,-0.000146720f,-0.000052355f};
};
#endif