Skip to content

Commit

Permalink
Add FM network audio gain and optional pre- and de-emphasis.
Browse files Browse the repository at this point in the history
  • Loading branch information
g4klx committed Mar 14, 2021
1 parent fb0d1ef commit 75e8e95
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 74 deletions.
42 changes: 37 additions & 5 deletions Conf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,16 @@ m_pocsagLocalPort(0U),
m_pocsagNetworkModeHang(3U),
m_pocsagNetworkDebug(false),
m_fmNetworkEnabled(false),
m_fmNetworkFormat("MMDVM"),
m_fmNetworkProtocol("MMDVM"),
m_fmGatewayAddress(),
m_fmGatewayPort(0U),
m_fmLocalAddress(),
m_fmLocalPort(0U),
m_fmSampleRate(8000U),
m_fmPreEmphasis(true),
m_fmDeEmphasis(true),
m_fmTXAudioGain(1.0F),
m_fmRXAudioGain(1.0F),
m_fmNetworkModeHang(3U),
m_fmNetworkDebug(false),
m_ax25NetworkEnabled(false),
Expand Down Expand Up @@ -999,8 +1003,8 @@ bool CConf::read()
} else if (section == SECTION_FM_NETWORK) {
if (::strcmp(key, "Enable") == 0)
m_fmNetworkEnabled = ::atoi(value) == 1;
else if (::strcmp(key, "Format") == 0)
m_fmNetworkFormat = value;
else if (::strcmp(key, "Protocol") == 0)
m_fmNetworkProtocol = value;
else if (::strcmp(key, "LocalAddress") == 0)
m_fmLocalAddress = value;
else if (::strcmp(key, "LocalPort") == 0)
Expand All @@ -1011,6 +1015,14 @@ bool CConf::read()
m_fmGatewayPort = (unsigned int)::atoi(value);
else if (::strcmp(key, "SampleRate") == 0)
m_fmSampleRate = (unsigned int)::atoi(value);
else if (::strcmp(key, "PreEmphasis") == 0)
m_fmPreEmphasis = ::atoi(value) == 1;
else if (::strcmp(key, "DeEmphasis") == 0)
m_fmDeEmphasis = ::atoi(value) == 1;
else if (::strcmp(key, "TXAudioGain") == 0)
m_fmTXAudioGain = float(::atof(value));
else if (::strcmp(key, "RXAudioGain") == 0)
m_fmRXAudioGain = float(::atof(value));
else if (::strcmp(key, "ModeHang") == 0)
m_fmNetworkModeHang = (unsigned int)::atoi(value);
else if (::strcmp(key, "Debug") == 0)
Expand Down Expand Up @@ -2200,9 +2212,9 @@ bool CConf::getFMNetworkEnabled() const
return m_fmNetworkEnabled;
}

std::string CConf::getFMNetworkFormat() const
std::string CConf::getFMNetworkProtocol() const
{
return m_fmNetworkFormat;
return m_fmNetworkProtocol;
}

std::string CConf::getFMGatewayAddress() const
Expand Down Expand Up @@ -2230,6 +2242,26 @@ unsigned int CConf::getFMSampleRate() const
return m_fmSampleRate;
}

bool CConf::getFMPreEmphasis() const
{
return m_fmPreEmphasis;
}

bool CConf::getFMDeEmphasis() const
{
return m_fmDeEmphasis;
}

float CConf::getFMTXAudioGain() const
{
return m_fmTXAudioGain;
}

float CConf::getFMRXAudioGain() const
{
return m_fmRXAudioGain;
}

unsigned int CConf::getFMNetworkModeHang() const
{
return m_fmNetworkModeHang;
Expand Down
12 changes: 10 additions & 2 deletions Conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,16 @@ class CConf

// The FM Network section
bool getFMNetworkEnabled() const;
std::string getFMNetworkFormat() const;
std::string getFMNetworkProtocol() const;
std::string getFMGatewayAddress() const;
unsigned int getFMGatewayPort() const;
std::string getFMLocalAddress() const;
unsigned int getFMLocalPort() const;
unsigned int getFMSampleRate() const;
bool getFMPreEmphasis() const;
bool getFMDeEmphasis() const;
float getFMTXAudioGain() const;
float getFMRXAudioGain() const;
unsigned int getFMNetworkModeHang() const;
bool getFMNetworkDebug() const;

Expand Down Expand Up @@ -604,12 +608,16 @@ class CConf
bool m_pocsagNetworkDebug;

bool m_fmNetworkEnabled;
std::string m_fmNetworkFormat;
std::string m_fmNetworkProtocol;
std::string m_fmGatewayAddress;
unsigned int m_fmGatewayPort;
std::string m_fmLocalAddress;
unsigned int m_fmLocalPort;
unsigned int m_fmSampleRate;
bool m_fmPreEmphasis;
bool m_fmDeEmphasis;
float m_fmTXAudioGain;
float m_fmRXAudioGain;
unsigned int m_fmNetworkModeHang;
bool m_fmNetworkDebug;

Expand Down
63 changes: 38 additions & 25 deletions FMControl.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020,2021 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -31,29 +31,36 @@ const float PREEMPHASIS_GAIN_DB = 0.0F; // Audio gain adjustment
const float FILTER_GAIN_DB = 2.0F; // Audio gain adjustment
const unsigned int FM_MASK = 0x00000FFFU;

CFMControl::CFMControl(CFMNetwork* network) :
CFMControl::CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn) :
m_network(network),
m_txAudioGain(txAudioGain),
m_rxAudioGain(rxAudioGain),
m_preEmphasisOn(preEmphasisOn),
m_deEmphasisOn(deEmphasisOn),
m_enabled(false),
m_incomingRFAudio(1600U, "Incoming RF FM Audio"),
m_preemphasis (NULL),
m_deemphasis (NULL),
m_preEmphasis(NULL),
m_deEmphasis(NULL),
m_filterStage1(NULL),
m_filterStage2(NULL),
m_filterStage3(NULL)
{
m_preemphasis = new CIIRDirectForm1Filter(8.315375384336983F,-7.03334621603483F,0.0F,1.0F,0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB);
m_deemphasis = new CIIRDirectForm1Filter(0.07708787090460224F,0.07708787090460224F,0.0F,1.0F,-0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB);
assert(txAudioGain > 0.0F);
assert(rxAudioGain > 0.0F);

//cheby type 1 0.2dB cheby type 1 3rd order 300-2700Hz fs=8000
m_preEmphasis = new CIIRDirectForm1Filter(8.315375384336983F, -7.03334621603483F,0.0F,1.0F, 0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB);
m_deEmphasis = new CIIRDirectForm1Filter(0.07708787090460224F, 0.07708787090460224F,0.0F, 1.0F, -0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB);

// Chebyshev type 1 0.2dB cheby type 1 3rd order 300-2700Hz fs=8000
m_filterStage1 = new CIIRDirectForm1Filter(0.29495028f, 0.0f, -0.29495028f, 1.0f, -0.61384624f, -0.057158668f, FILTER_GAIN_DB);
m_filterStage2 = new CIIRDirectForm1Filter(1.0f, 2.0f, 1.0f, 1.0f, 0.9946123f, 0.6050482f, FILTER_GAIN_DB);
m_filterStage3 = new CIIRDirectForm1Filter(1.0f, -2.0f, 1.0f, 1.0f, -1.8414584f, 0.8804949f, FILTER_GAIN_DB);
}

CFMControl::~CFMControl()
{
delete m_preemphasis ;
delete m_deemphasis ;
delete m_preEmphasis;
delete m_deEmphasis;

delete m_filterStage1;
delete m_filterStage2;
Expand All @@ -79,42 +86,45 @@ bool CFMControl::writeModem(const unsigned char* data, unsigned int length)

m_incomingRFAudio.addData(data + 1U, length - 1U);
unsigned int bufferLength = m_incomingRFAudio.dataSize();
if (bufferLength > 240U) //160 samples 12-bit
bufferLength = 240U; //160 samples 12-bit
if (bufferLength > 240U) // 160 samples 12-bit
bufferLength = 240U; // 160 samples 12-bit

if (bufferLength >= 3U) {
bufferLength = bufferLength - bufferLength % 3U; //round down to nearest multiple of 3
unsigned char bufferData[240U]; //160 samples 12-bit
bufferLength = bufferLength - bufferLength % 3U; // Round down to nearest multiple of 3
unsigned char bufferData[240U]; // 160 samples 12-bit
m_incomingRFAudio.getData(bufferData, bufferLength);

unsigned int pack = 0U;
unsigned char* packPointer = (unsigned char*)&pack;
float out[160U]; //160 samples 12-bit
float out[160U]; // 160 samples 12-bit
unsigned int nOut = 0U;
short unpackedSamples[2U];

for (unsigned int i = 0U; i < bufferLength; i += 3U) {
//extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed
packPointer[0U] = bufferData[i];
// Extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed
packPointer[0U] = bufferData[i + 0U];
packPointer[1U] = bufferData[i + 1U];
packPointer[2U] = bufferData[i + 2U];

unpackedSamples[1U] = short(int(pack & FM_MASK) - 2048);
unpackedSamples[0U] = short(int(pack >> 12 & FM_MASK) - 2048); //

//process unpacked sample pair
// Process unpacked sample pair
for (unsigned char j = 0U; j < 2U; j++) {
// Convert to float (-1.0 to +1.0)
float sampleFloat = float(unpackedSamples[j]) / 2048.0F;
float sampleFloat = (float(unpackedSamples[j]) * m_rxAudioGain) / 2048.0F;

// De-emphasise and remove CTCSS
sampleFloat = m_deemphasis->filter(sampleFloat);
if (m_deEmphasisOn)
sampleFloat = m_deEmphasis->filter(sampleFloat);

out[nOut++] = m_filterStage3->filter(m_filterStage2->filter(m_filterStage1->filter(sampleFloat)));
}
}

#if defined(DUMP_RF_AUDIO)
FILE * audiofile = fopen("./audiodump.bin", "ab");
if(audiofile != NULL) {
if (audiofile != NULL) {
fwrite(out, sizeof(float), nOut, audiofile);
fclose(audiofile);
}
Expand All @@ -133,8 +143,8 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space)
if (m_network == NULL)
return 0U;

if (space > 240U) //160 samples 12-bit
space = 240U; //160 samples 12-bit
if (space > 240U) // 160 samples 12-bit
space = 240U; // 160 samples 12-bit

float netData[160U]; // Modem can handle up to 160 samples at a time
unsigned int length = m_network->read(netData, 160U); //160 samples 12-bit
Expand All @@ -146,14 +156,17 @@ unsigned int CFMControl::readModem(unsigned char* data, unsigned int space)
unsigned int nData = 0U;

for (unsigned int i = 0; i < length; i++) {
float sampleFloat = netData[i] * m_txAudioGain;

// Pre-emphasis
float sampleFloat = m_preemphasis->filter(netData[i]);
if (m_preEmphasisOn)
sampleFloat = m_preEmphasis->filter(sampleFloat);

// Convert float to 12-bit samples (0 to 4095)
unsigned int sample12bit = (unsigned int)((sampleFloat + 1.0F) * 2048.0F + 0.5F);

// pack 2 samples onto 3 bytes
if((i & 1U) == 0) {
// Pack 2 samples into 3 bytes
if ((i & 1U) == 0) {
pack = 0U;
pack = sample12bit << 12;
} else {
Expand Down
12 changes: 8 additions & 4 deletions FMControl.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020,2021 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -30,7 +30,7 @@

class CFMControl {
public:
CFMControl(CFMNetwork* network);
CFMControl(CFMNetwork* network, float txAudioGain, float rxAudioGain, bool preEmphasisOn, bool deEmphasisOn);
~CFMControl();

bool writeModem(const unsigned char* data, unsigned int length);
Expand All @@ -43,10 +43,14 @@ class CFMControl {

private:
CFMNetwork* m_network;
float m_txAudioGain;
float m_rxAudioGain;
bool m_preEmphasisOn;
bool m_deEmphasisOn;
bool m_enabled;
CRingBuffer<unsigned char> m_incomingRFAudio;
CIIRDirectForm1Filter* m_preemphasis;
CIIRDirectForm1Filter* m_deemphasis;
CIIRDirectForm1Filter* m_preEmphasis;
CIIRDirectForm1Filter* m_deEmphasis;
CIIRDirectForm1Filter* m_filterStage1;
CIIRDirectForm1Filter* m_filterStage2;
CIIRDirectForm1Filter* m_filterStage3;
Expand Down
32 changes: 16 additions & 16 deletions FMNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@

const unsigned int BUFFER_LENGTH = 500U;

CFMNetwork::CFMNetwork(const std::string& format, const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug) :
m_format(FMF_MMDVM),
CFMNetwork::CFMNetwork(const std::string& protocol, const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug) :
m_protocol(FMNP_MMDVM),
m_socket(localAddress, localPort),
m_addr(),
m_addrLen(0U),
Expand All @@ -46,8 +46,8 @@ m_seqNo(0U)
if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0)
m_addrLen = 0U;

if (format == "USRP")
m_format = FMF_USRP;
if (protocol == "USRP")
m_protocol = FMNP_USRP;

#if !defined(_WIN32) && !defined(_WIN64)
int error;
Expand Down Expand Up @@ -116,7 +116,7 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples)

unsigned int length = 0U;

if (m_format == FMF_USRP) {
if (m_protocol == FMNP_USRP) {
buffer[length++] = 'U';
buffer[length++] = 'S';
buffer[length++] = 'R';
Expand Down Expand Up @@ -187,7 +187,7 @@ bool CFMNetwork::writeData(float* data, unsigned int nSamples)

bool CFMNetwork::writeEOT()
{
if (m_format == FMF_MMDVM) {
if (m_protocol == FMNP_MMDVM) {
unsigned char buffer[10U];
::memset(buffer, 0x00U, 10U);

Expand Down Expand Up @@ -220,19 +220,19 @@ void CFMNetwork::clock(unsigned int ms)
if (length <= 0)
return;

// Check if the data is for us // does not accept data from USRP
//if (!CUDPSocket::match(addr, m_addr)) {
// LogMessage("FM packet received from an invalid source");
// return;
//}
// Check if the data is for us
if (!CUDPSocket::match(addr, m_addr)) {
LogMessage("FM packet received from an invalid source");
return;
}

if (!m_enabled)
return;

if (m_debug)
CUtils::dump(1U, "FM Network Data Received", buffer, length);

if (m_format == FMF_USRP) {
if (m_protocol == FMNP_USRP) {
// Invalid packet type?
if (::memcmp(buffer, "USRP", 4U) != 0)
return;
Expand Down Expand Up @@ -304,9 +304,9 @@ unsigned int CFMNetwork::read(float* data, unsigned int nSamples)
} else {
#endif
for (unsigned int i = 0U; i < nSamples; i++) {
short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + // Changing audio format from U16BE to S16LE
((buffer[i * 2U + 1U] & 0xFFU) << 8); // Changing audio format from U16BE to S16LE
data[i] = (float(val) / 65536.0F); // Changing audio format from U16BE to S16LE
short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) +
((buffer[i * 2U + 1U] & 0xFFU) << 8);
data[i] = float(val) / 65536.0F;
}

return nSamples;
Expand Down Expand Up @@ -347,7 +347,7 @@ void CFMNetwork::enable(bool enabled)

void CFMNetwork::writePoll()
{
if (m_format == FMF_MMDVM) {
if (m_protocol == FMNP_MMDVM) {
unsigned char buffer[3U];

buffer[0U] = 'F';
Expand Down
Loading

0 comments on commit 75e8e95

Please sign in to comment.