diff --git a/TeensyDSP/DSP.cpp b/TeensyDSP/DSP.cpp index 21ce890..d35e896 100644 --- a/TeensyDSP/DSP.cpp +++ b/TeensyDSP/DSP.cpp @@ -56,7 +56,8 @@ AudioControlSGTL5000 audioCtrl; //xy=435,448 //} audio; -UBitxDSP::UBitxDSP() { +UBitxDSP::UBitxDSP() +: txSrc(MIC_IN), txSrcLatched(MIC_IN) { } void UBitxDSP::begin() { @@ -67,47 +68,12 @@ void UBitxDSP::begin() { audioCtrl.volume(0.0); // headphone volume... audioCtrl.muteHeadphone(); // ...not used by UBitxDSP - for (int i = 0; i < 4; i++) { - if (i == RX_AUDIO_CH) - rxAudio.gain(i, 1.0); - else - rxAudio.gain(i, 0.0); - } - - for (int i = 0; i < 4; i++) { - txAudio.gain(i, 0.0); - } - - // SETUP THE AUDIO INPUTS - - // Rig (Line) Input (RX) - audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); - audioCtrl.unmuteLineout(); - audioCtrl.lineInLevel(9, 5); // RX, TX - audioCtrl.lineOutLevel(29, 31); // RX, TX - - // Mic Input (TX) - audioCtrl.micGain(0); // TODO: set value - - // Line Input (TX) - - // USB Input (TX) - - // SETUP THE AUDIO OUTPUTS - - // Line Output (RX) - lineOutAmp.gain(1.0); - - // USB Output (RX) - usbOutAmp.gain(1.0); - usbBypassAmp.gain(1.0); - - // Rig (Line) Output (TX) - txOutAmp.gain(1.0); + setupRxAudio(); + setupTxAudio(); // Default to RX. - muteRxIn(); - muteTxIn(); + muteRxIn(); // redundant? + muteTxIn(); // redundant? isTx = true; // so that rx() call works rx(); @@ -120,56 +86,123 @@ void UBitxDSP::begin() { } void UBitxDSP::update() { - // Only going to adjust the USB volume periodically. + // Update the USB volume (level of TX USB output) periodically. if (sinceLastUpdate > DSP_MILLIS_PER_UPDATE) { float vol = usbIn.volume(); - setTxInLevel(TX_USB, vol); + if (vol != usbVol) { + setTxInLevel(TX_USB, vol); + usbVol = vol; + } sinceLastUpdate = 0; } - // Update the VOX switches. - // TODO: Move the enable logic in here, so we don't process unnecessarily. - if (txVoxLevel.available()) { - if (txVoxLevel.read() > state.vox.threshold) { - state.vox.timeout = millis() + state.vox.delay; - state.vox.active = true; + if (state.vox[txSrc].enable && txVoxLevel.available()) { + if (txVoxLevel.read() > state.vox[txSrc].threshold) { + voxTimeout = millis() + state.voxDelay; + state.voxActive = true; } } - if (millis() > state.vox.timeout) { - state.vox.active = false; + if (millis() > voxTimeout) { + state.voxActive = false; } } void UBitxDSP::end() { } +//====================================================================== +// Transmit/Receive (T/R) Switching +//====================================================================== + +/*! + * @brief Revert to receive (RX) mode from transmit (TX) mode. First + * the transmit audio output is muted, to ensure that no more + * audio goes to the rig. Then we check to see if the latched + * TX audio source was different than the selected TX audio + * source; this happens if the radio is currently set for a + * particular input (which determines what is monitored by the + * VOX), but then is commanded to transmit a different source + * (e.g. based on a CAT command). The actual transmit audio + * source is latched during transmit, but upon returning to + * receive, we restore the selected transmit audio. + */ void UBitxDSP::rx() { if (isTx) { - muteTxIn(); - audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); + muteTxOut(); + if (txSrcLatched != txSrc) { + setTxAudioIn(txSrc); + } + if (txSrcLatched == MIC_IN) { + audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); + } unmuteRxIn(RX_AUDIO); isTx = false; } } +void UBitxDSP::tx(TxAudioIn src) { + if (!isTx) { + muteRxIn(RX_AUDIO); + + txSrcLatched = src; + if (txSrcLatched != txSrc) { + setTxAudioIn(txSrcLatched, true); + } + + if (txSrcLatched == MIC_IN) { + audioCtrl.inputSelect(AUDIO_INPUT_MIC); + audioCtrl.micGain(12); // TODO: Make this dynamic + } + + unmuteTxOut(); + isTx = true; + } +} + +//====================================================================== +// Receive (RX) Audio Chain +//====================================================================== + +void UBitxDSP::setupRxAudio() { + for (int i = 0; i < NUM_RX_AUDIO_CH; i++) { + if (i == RX_AUDIO) + rxAudio.gain(i, 1.0); + else + rxAudio.gain(i, 0.0); + } + + // Rig (Line) Input (RX) + audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); + audioCtrl.unmuteLineout(); + audioCtrl.lineInLevel(9, 5); // RX, TX + audioCtrl.lineOutLevel(29, 31); // RX, TX + + // Line Output (RX) + lineOutAmp.gain(1.0); + + // USB Output (RX) + usbOutAmp.gain(1.0); + usbBypassAmp.gain(1.0); +} + void UBitxDSP::setRxInLevel(RxAudioCh ch, float level) { if (ch < NUM_RX_AUDIO_CH) { - state.rx[ch].level = level; - rxAudio.gain(ch, state.rx[ch].mute ? 0.0 : state.rx[ch].level); + state.rxin[ch].level = level; + rxAudio.gain(ch, state.rxin[ch].mute ? 0.0 : state.rxin[ch].level); } } void UBitxDSP::muteRxIn() { - for (RxAudioCh i = RX_AUDIO; i < NUM_RX_AUDIO_CH; i++) { - state.rx[ch].mute = true; - rxAudio.gain(ch, 0.0); + for (int i = 0; i < NUM_RX_AUDIO_CH; i++) { + state.rxin[RxAudioCh(i)].mute = true; + rxAudio.gain(i, 0.0); } } void UBitxDSP::muteRxIn(RxAudioCh ch) { if (ch < NUM_RX_AUDIO_CH) { - if (!state.rx[ch].mute) { - state.rx[ch].mute = true; + if (!state.rxin[ch].mute) { + state.rxin[ch].mute = true; rxAudio.gain(ch, 0.0); } } @@ -177,70 +210,58 @@ void UBitxDSP::muteRxIn(RxAudioCh ch) { void UBitxDSP::unmuteRxIn(RxAudioCh ch) { if (ch < NUM_RX_AUDIO_CH) { - if (state.rx[ch].mute) { - state.rx[ch].mute = false; - rxAudio.gain(ch, state.rx[ch].level); + if (state.rxin[ch].mute) { + state.rxin[ch].mute = false; + rxAudio.gain(ch, state.rxin[ch].level); } } } -void UBitxDSP::tx(TxAudioIn src) { - if (!isTx) { - muteRxIn(RX_AUDIO); - - switch (src) { - case MIC_IN: - audioCtrl.inputSelect(AUDIO_INPUT_MIC); - audioCtrl.micGain(12); // TODO: Make this dynamic - unmuteTxIn(TX_LINE); +//====================================================================== +// Transmit (TX) Audio Chain +//====================================================================== - case LINE_IN: - audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); - unmuteTxIn(TX_LINE); - break; - - case USB_IN: - unmuteTxIn(TX_USB); - break; - - case TUNE_IN: - tone1.amplitude(1.0); // TODO - just do this once. - tone1.frequency(1500); // TODO: Make this dynamic based on CW (sidetone freq) versus data (1500 Hz) - unmuteTxIn(TX_TONE1); - break; - - case TWO_TONE_IN: - tone1.amplitude(1.0); // TODO - just do this once. - tone1.amplitude(1.0); // TODO - just do this once. - tone1.frequency(700); - tone2.frequency(1900); - unmuteTxIn(TX_TONE1); - unmuteTxIn(TX_TONE2); - break; - } - - isTx = true; +void UBitxDSP::setupTxAudio() { + for (int i = 0; i < NUM_TX_AUDIO_CH; i++) { + txAudio.gain(i, 0.0); } + + // Mic Input (TX) + audioCtrl.micGain(0); // TODO: set value + // Line Input (TX) + + // USB Input (TX) + + // Rig (Line) Output (TX) + txOutAmp.gain(1.0); + + tone1.amplitude(1.0); // TODO - just do this once. + tone1.frequency(1500); // TODO: Make this dynamic based on CW (sidetone freq) versus data (1500 Hz) + + tone1.amplitude(1.0); // TODO - just do this once. + tone1.amplitude(1.0); // TODO - just do this once. + tone1.frequency(700); + tone2.frequency(1900); } void UBitxDSP::setTxInLevel(TxAudioCh ch, float level) { if (ch < NUM_TX_AUDIO_CH) { - state.tx[ch].level = level; - txAudio.gain(ch, state.tx[ch].mute ? 0.0 : state.tx[ch].level); + state.txin[ch].level = level; + txAudio.gain(ch, state.txin[ch].mute ? 0.0 : state.txin[ch].level); } } void UBitxDSP::muteTxIn() { - for (TxAudioCh i = TX_LINE; i < NUM_TX_AUDIO_CH; i++) { - state.tx[ch].mute = true; - txAudio.gain(ch, 0.0); + for (int i = 0; i < NUM_TX_AUDIO_CH; i++) { + state.txin[TxAudioCh(i)].mute = true; + txAudio.gain(i, 0.0); } } void UBitxDSP::muteTxIn(TxAudioCh ch) { if (ch < NUM_TX_AUDIO_CH) { - if (!state.tx[ch].mute) { - state.tx[ch].mute = true; + if (!state.txin[ch].mute) { + state.txin[ch].mute = true; txAudio.gain(ch, 0.0); } } @@ -248,15 +269,82 @@ void UBitxDSP::muteTxIn(TxAudioCh ch) { void UBitxDSP::unmuteTxIn(TxAudioCh ch) { if (ch < NUM_TX_AUDIO_CH) { - if (state.tx[ch].mute) { - state.tx[ch].mute = false; - rxAudio.gain(ch, state.tx[ch].level); + if (state.txin[ch].mute) { + state.txin[ch].mute = false; + rxAudio.gain(ch, state.txin[ch].level); } } } -/**********************************************************************/ -// RX filter settings +void UBitxDSP::setTxOutLevel(float level) { + state.txout.level = level; + txOutAmp.gain(state.txout.mute ? 0.0 : state.txout.level); +} + +void UBitxDSP::muteTxOut() { + if (!state.txout.mute) { + state.txout.mute = true; + txOutAmp.gain(0.0); + } +} + +void UBitxDSP::unmuteTxOut() { + if (state.txout.mute) { + state.txout.mute = false; + txOutAmp.gain(state.txout.level); + } +} + +void UBitxDSP::setTxAudioIn(TxAudioIn src, bool isTemp) { + if (!isTemp) { + txSrc = src; + } + + if (!isTx) { + muteTxIn(); // Mute all channels, then unmute the desired ones. + switch (src) { // Don't switch inputs while transmitting. + case MIC_IN: + // Note that we can't actually use the VOX code on the mic input, + // because we can't make the actual mic input active without + // losing our receive audio. So, mic input is never actually + // selected until it is time for it to transmit, which makes the + // VOX moot. The caller must make use of an external, analog VOX + // circuit driving a GPIO pin, or something similar (or the PTT of + // course) to begin actually using the mic input. So this case + // just falls through to the line input. + + case LINE_IN: + unmuteTxIn(TX_LINE); + break; + + case USB_IN: + unmuteTxIn(TX_USB); + break; + + case TUNE_IN: + tone1.amplitude(1.0); // TODO - just do this once. + tone1.frequency(1500); // TODO: Make this dynamic based on CW (sidetone freq) versus data (1500 Hz) + unmuteTxIn(TX_TONE1); + break; + + case TWO_TONE_IN: + tone1.amplitude(1.0); // TODO - just do this once. + tone1.amplitude(1.0); // TODO - just do this once. + tone1.frequency(700); + tone2.frequency(1900); + unmuteTxIn(TX_TONE1); + unmuteTxIn(TX_TONE2); + break; + + default: + // should never happen + } + } +} + +//====================================================================== +// RX Audio Filter Settings +//====================================================================== const int minRxFilterLo = MIN_RX_FILTER_LO; const int maxRxFilterHi = MAX_RX_FILTER_HI; @@ -266,8 +354,8 @@ const int minRxFilterCenter = MIN_RX_FILTER_CENTER; const int maxRxFilterCenter = MAX_RX_FILTER_CENTER; /*! - @brief Bypass the RX audio filter. -*/ + * @brief Bypass the RX audio filter. + */ void UBitxDSP::bypassRxFilter() { rxFilter.begin(FIR_PASSTHRU, NUM_COEFFICIENTS); } @@ -346,50 +434,6 @@ void UBitxDSP::setRxFilterCenter(int center) { setRxFilter(lo, hi); } - -/**********************************************************************/ -// TX audio input settings - -void UBitxDSP::setTxInputLevel(int ch, float lvl) { - if ((ch > -1) && (ch < txNumChannels)) { - state.txInLvl[ch] = lvl; - float vol = lvl * float(state.txInEnable[ch] * state.txInTx[ch]); - txAudio.gain(ch, vol); - } -} - -void UBitxDSP::enableTxInput(int ch) { - if ((ch > -1) && (ch < txNumChannels)) { - state.txInEnable[ch] = 1; - float vol = state.txInLvl[ch] * float(state.txInEnable[ch] * state.txInTx[ch]); - txAudio.gain(ch, vol); - } -} - -void UBitxDSP::disableTxInput(int ch) { - if ((ch > -1) && (ch < txNumChannels)) { - state.txInEnable[ch] = 0; - float vol = state.txInLvl[ch] * float(state.txInEnable[ch] * state.txInTx[ch]); - txAudio.gain(ch, vol); - } -} - -void UBitxDSP::startTxInput(int ch) { - if ((ch > -1) && (ch < txNumChannels)) { - state.txInTx[ch] = 1; - float vol = state.txInLvl[ch] * float(state.txInEnable[ch] * state.txInTx[ch]); - txAudio.gain(ch, vol); - } -} - -void UBitxDSP::stopTxInput(int ch) { - if ((ch > -1) && (ch < txNumChannels)) { - state.txInTx[ch] = 0; - float vol = state.txInLvl[ch] * float(state.txInEnable[ch] * state.txInTx[ch]); - txAudio.gain(ch, vol); - } -} - /* NOTES diff --git a/TeensyDSP/DSP.h b/TeensyDSP/DSP.h index fb707b2..9a6e4d5 100644 --- a/TeensyDSP/DSP.h +++ b/TeensyDSP/DSP.h @@ -25,53 +25,19 @@ #define TX_USB_IN_CH 1 #define TX_NUM_CHANNELS 2 -#define TX_VOX_USB_THRESH 0.25 +#define TX_VOX_MIC_THRESH 0.0 #define TX_VOX_LINE_THRESH 0.25 +#define TX_VOX_USB_THRESH 0.25 +#define TX_VOX_TUNE_THRESH 0.0 +#define TX_VOX_TT_THRESH 0.0 #define TX_VOX_DELAY 500 -struct DSPState { - - // RX audio output settings - - // RX filter settings - int rxFilterLo = 300; - int rxFilterHi = 3000; - - // TX audio input settings - float txInLvl[4] = {0.5, 0.5, 0.0, 0.0}; - int txInEnable[4] = {1, 1, 0, 0}; - int txInTx[4] = {0, 0, 0, 0}; - - struct { - bool mute = true; - float level = 0.1; - } tx[NUM_TX_AUDIO_CH]; - - struct { - bool mute = true; - float level = 1.0; - } rx[NUM_RX_AUDIO_CH]; - - // VOX settings -/* struct { - bool isActive = false; - bool isUSB = false; - float usbThresh = TX_VOX_USB_THRESH; - float lineLevel = TX_VOX_LINE_THRESH; - unsigned delay = TX_VOX_DELAY;*/ - unsigned voxTimeout[3]; -}; - -enum TRState { - TRANSMIT, - RECEIVE -}; - -enum RxAudioIn { - RIG_IN = 0, - NUM_RX_AUDIO_IN -}; +/**********************************************************************/ +/*! + * @brief Defines the four separate RX audio input channels available. + * Not all are currently used by the implementation. + */ enum RxAudioCh { RX_AUDIO = 0, RX_SPARE1, @@ -80,6 +46,36 @@ enum RxAudioCh { NUM_RX_AUDIO_CH }; +/*! + * @brief Defines the different RX audio inputs. This is not really + * different than the channels, but in theory could be. See + * the corresponding TX enums for an example of this. + */ +enum RxAudioIn { + RIG_IN = 0, + NUM_RX_AUDIO_IN +}; + +/*! + * @brief Defines the four separate TX audio input channels available. + * They do not directly correlate to TX audio input sources. + * Specifically. both mic and line inputs use TX_LINE, and + * the two-tone input uses both TX_TONE1 and TX_TONE2 at the + * same time. + */ +enum TxAudioCh { + TX_LINE = 0, + TX_USB, + TX_TONE1, + TX_TONE2, + NUM_TX_AUDIO_CH +}; + +/*! + * @brief Defines the different TX audio input sources (not channels!) + * As noted above, MIC_IN and LINE_IN share channels, and + * TWO_TONE_IN uses multiple channels. + */ enum TxAudioIn { MIC_IN = 0, LINE_IN, @@ -89,30 +85,122 @@ enum TxAudioIn { NUM_TX_AUDIO_IN }; -enum TxAudioCh { - TX_LINE = 0, - TX_USB, - TX_TONE1, - TX_TONE2, - NUM_TX_AUDIO_CH +/*! + * @brief Describes a simple audio channel that can be muted. + */ +struct AudioChannel { + bool mute = false; + float level = 0.0; }; +/**********************************************************************/ + +struct DSPState { + + // RX audio I/O settings - default to muted + AudioChannel rxin[NUM_RX_AUDIO_CH] = { + {true, 1.0}, // audio + {true, 0.0}, // spare 1 + {true, 0.0}, // spare 2 + {true, 0.0} // spare 3 + }; + AudioChannel rxout = {true, 1.0}; + + // TX audio I/O settings - default to muted + AudioChannel txin[NUM_TX_AUDIO_CH] = { + {true, 0.1}, // line + {true, 0.1}, // USB + {true, 0.1}, // tone 1 + {true, 0.1} // tone 2 + }; + AudioChannel txout = {true, 1.0}; + + // TX VOX settings, per audio input (not channel!) + struct { + bool enable = false; + float threshold = 1.0; + } vox[NUM_TX_AUDIO_IN] = { + {false, TX_VOX_MIC_THRESH}, + {true, TX_VOX_LINE_THRESH}, + {true, TX_VOX_USB_THRESH}, + {false, TX_VOX_TUNE_THRESH}, + {false, TX_VOX_TT_THRESH} + }; + unsigned voxDelay = TX_VOX_DELAY; + bool voxActive = false; + + // RX filter settings + int rxFilterLo = 300; + int rxFilterHi = 3000; +}; + + class UBitxDSP { - public: + + // Object construction. + public: UBitxDSP(); + + // Basic administrative functions. + public: void begin(); void update(); - void end(); + void end(); + + //==================================================================== + // Transmit/Receive (T/R) Switching + //==================================================================== + + public: void rx(); inline void tx() { tx(txSrc); } void tx(TxAudioIn src); - inline void setTxAudioIn(TxAudioIn src) { txSrc = src; } - TxAudionIn getTxAudioIn() const { return txSrc; } + //==================================================================== + // Receive (RX) Audio Chain + //==================================================================== - // RX audio output settings + // General administrative function to setup the initial RX audio + // chain. This should only be called once, via begin(). + private: + void setupRxAudio(); - // RX filter settings + // Basic control of RX audio inputs and outputs. + public: + void setRxInLevel(RxAudioCh ch, float level); // Set the audio input level for a given channel. + void muteRxIn(); // Mute all RX audio input channels. + void muteRxIn(RxAudioCh ch); // Mute a specific RX audio input channel. + void unmuteRxIn(RxAudioCh ch); // Un-mute a specific RX audio input channel. + + //==================================================================== + // Transmit (TX) Audio Chain + //==================================================================== + + // General administrative function to setup the initial TX audio + // chain. This should only be called once, via begin(). + private: + void setupTxAudio(); + + // Basic control of TX audio inputs and outputs. + public: + void setTxInLevel(TxAudioCh ch, float level); // Set the audio input level for a given channel. + void muteTxIn(); // Mute all TX audio input channels. + void muteTxIn(TxAudioCh ch); // Mute a specific TX audio input channel. + void unmuteTxIn(TxAudioCh ch); // Un-mute a specific TX audio input channel. + void setTxOutLevel(float level); // Set the TX audio output level. + void muteTxOut(); // Mute the TX audio output. + void unmuteTxOut(); // Un-mute the TX audio output. + + // Transmit audio selection (may be overriden at actual transmit time). + public: + void setTxAudioIn(TxAudioIn src, bool isTemp = false); // Select a specific TX audio input path, and identify it as permanent or temporary. + inline TxAudioIn getTxAudioIn() const { return txSrc; } // Return the current TX audio input. + + //==================================================================== + // RX Audio Filter Settings + //==================================================================== + + public: void bypassRxFilter(); void setRxFilter(int lo, int hi); void setRxFilterLo(int lo); @@ -151,20 +239,6 @@ class UBitxDSP { void startTxInput(int ch); void stopTxInput(int ch); - // VOX settings - inline void setDataVoxOn() { state.vox.isActive = true; } - inline void setDataVoxOff() { state.vox.isActive = false; } - inline void setDataVoxThreshold(float val) { - - } - inline void setDataVoxThreshold(float val, bool isUSB) { - if (isUSB) { - state.vox.usbThresh = val; - } else { - state.vox.lineThresh = val; - } - } - private: bool isTx = false; @@ -175,7 +249,8 @@ class UBitxDSP { short coefficients[NUM_COEFFICIENTS]; elapsedMillis sinceLastUpdate; - TxAudioIn txSrc; + TxAudioIn txSrc, txSrcLatched; + float usbVol = 0.0; unsigned voxTimeout; }; diff --git a/TeensyDSP/TR.cpp b/TeensyDSP/TR.cpp index 826df64..3d30f98 100644 --- a/TeensyDSP/TR.cpp +++ b/TeensyDSP/TR.cpp @@ -36,15 +36,15 @@ void UBitxTR::update(bool cw, bool extKey) { } else { if ((pttEnable && pttPressed()) || (voxEnable && voxActivated())) { // first, setup TX audio; then, start transmitting (from Mic) - DBGCMD( dsp.txMicIn() ); + DBGCMD( dsp.tx(MIC_IN) ); DBGCMD( setTX() ); } else if (keyEnable && keyPressed()) { // first, setup TX audio; then, start transmitting (from Line In) - DBGCMD( dsp.txLineIn() ); + DBGCMD( dsp.tx(LINE_IN) ); DBGCMD( setTX() ); } else if (catEnable && catActivated()) { // first, setup TX audio; then, start transmitting (USB) - DBGCMD( dsp.txUSBIn() ); + DBGCMD( dsp.tx(USB_IN) ); DBGCMD( setTX() ); } }