diff --git a/code/src/PhoenixSketch/CAT.cpp b/code/src/PhoenixSketch/CAT.cpp index 376951a..d09f23a 100644 --- a/code/src/PhoenixSketch/CAT.cpp +++ b/code/src/PhoenixSketch/CAT.cpp @@ -1,4 +1,5 @@ #include "CAT.h" +#include "MainBoard_AudioIO.h" // GetFt8Mode() // Kenwood TS-480 CAT Interface (partial) // @@ -693,10 +694,22 @@ char *RX_write( char* cmd ){ char *TX_write( char* cmd ){ switch (modeSM.state_id){ case (ModeSm_StateId_SSB_RECEIVE):{ +#ifdef T41_USB_AUDIO + // Only allow CAT PTT (WSJT-X TX) when FT8 mode is enabled + if (GetFt8Mode()) { + ModeSm_dispatch_event(&modeSM, ModeSm_EventId_PTT_PRESSED); + } else { + // Ignore TX request if not in FT8 mode + // (optional) Serial.println("CAT TX ignored: FT8 mode is OFF"); + } +#else + // No USB audio build: allow normal CAT PTT behavior ModeSm_dispatch_event(&modeSM, ModeSm_EventId_PTT_PRESSED); +#endif break; } case (ModeSm_StateId_CW_RECEIVE):{ + // CW keying via CAT still allowed ModeSm_dispatch_event(&modeSM, ModeSm_EventId_KEY_PRESSED); break; } @@ -706,6 +719,7 @@ char *TX_write( char* cmd ){ return empty_string_p; } + char *VX_write( char* cmd ){ Debug("Got VX write"); return empty_string_p; @@ -740,7 +754,6 @@ char *PR_read( char* cmd ){ return obuf; } - /** * Poll SerialUSB1 for incoming CAT commands and process them * @@ -751,55 +764,56 @@ char *PR_read( char* cmd ){ void CheckForCATSerialEvents(void){ int i; char c; - while( ( i = SerialUSB1.available() ) > 0 ){ - c = ( char )SerialUSB1.read(); + +#ifdef T41_USB_AUDIO + while ( (i = Serial.available()) > 0 ) { + c = (char)Serial.read(); +#else + while ( (i = SerialUSB1.available()) > 0 ) { + c = (char)SerialUSB1.read(); +#endif i--; catCommand[ catCommandIndex ] = c; - #ifdef DEBUG_CAT + +#ifdef DEBUG_CAT Serial.print( catCommand[ catCommandIndex ] ); - #endif +#endif + if( c == ';' ){ - // Finished reading CAT command - #ifdef DEBUG_CAT +#ifdef DEBUG_CAT Serial.println(); - #endif // DEBUG_CAT - - // Check to see if the command is a good one BEFORE sending it - // to the command executor - //Serial.println( String("catCommand is ")+String(catCommand)+String(" catCommandIndex is ")+String(catCommandIndex)); +#endif char *parser_output = command_parser( catCommand ); catCommandIndex = 0; - // We executed it, now erase it memset( catCommand, 0, sizeof( catCommand )); + if( parser_output[0] != '\0' ){ - #ifdef DEBUG_CAT1 - Serial.println( parser_output ); - #endif // DEBUG_CAT int i = 0; while( parser_output[i] != '\0' ){ +#ifdef T41_USB_AUDIO + if( Serial.availableForWrite() > 0 ){ + Serial.print( parser_output[i] ); +#else if( SerialUSB1.availableForWrite() > 0 ){ SerialUSB1.print( parser_output[i] ); - #ifdef DEBUG_CAT - Serial.print( parser_output[i] ); - #endif +#endif i++; - }else{ - SerialUSB1.flush(); } } +#ifdef T41_USB_AUDIO + Serial.flush(); +#else SerialUSB1.flush(); - #ifdef DEBUG_CAT - Serial.println(); - #endif // DEBUG_CAT +#endif } - }else{ + } else { catCommandIndex++; if( catCommandIndex >= 128 ){ catCommandIndex = 0; - memset( catCommand, 0, sizeof( catCommand )); //clear out that overflowed buffer! - #ifdef DEBUG_CAT + memset( catCommand, 0, sizeof( catCommand )); +#ifdef DEBUG_CAT Serial.println( "CAT command buffer overflow" ); - #endif +#endif } } } diff --git a/code/src/PhoenixSketch/Config.h b/code/src/PhoenixSketch/Config.h index 380087a..7d67707 100644 --- a/code/src/PhoenixSketch/Config.h +++ b/code/src/PhoenixSketch/Config.h @@ -18,7 +18,7 @@ // Control encoder direction and speed #define FAST_TUNE // comment this out to disable the FAST_TUNE algorithm -#define VOLUME_REVERSED true +#define VOLUME_REVERSED true #define FILTER_REVERSED true #define MAIN_TUNE_REVERSED true #define FINE_TUNE_REVERSED false @@ -32,6 +32,8 @@ // Default uses AD7991 digital SWR. //#define USE_ANALOG_SWR +//#define ENABLE_DEBUG_SERIAL // TO ENABLE DEBUG UNCOMMENT THIS LINE AND SWITCH OFF FT8/DIGITAL NEED TO PROGRAM TEENSY AS DUAL SERIAL +//#define T41_USB_AUDIO 1 // TO ENABLE FT8 UNCOMMENT THIS LINE AND SWITCH ON FT8/DIGITAL NEED TO PROGRAM TEENSY AS SERIAL + MIDI + AUDIO. // CW configuration #define CW_TRANSMIT_SPACE_TIMEOUT_MS 200 // how long to wait for another key press before exiting CW transmit state diff --git a/code/src/PhoenixSketch/DSP.cpp b/code/src/PhoenixSketch/DSP.cpp index 47f7169..ca8aa25 100644 --- a/code/src/PhoenixSketch/DSP.cpp +++ b/code/src/PhoenixSketch/DSP.cpp @@ -1,4 +1,6 @@ #include "SDT.h" +#include "Ft8UsbBridge.h" +#include "MainBoard_AudioIO.h" // for GetFt8Mode() float32_t DMAMEM float_buffer_L[READ_BUFFER_SIZE]; float32_t DMAMEM float_buffer_R[READ_BUFFER_SIZE]; @@ -14,6 +16,11 @@ static char *filename = nullptr; void SaveData(DataBlock *data, uint32_t suffix); // used by the unit tests static uint32_t swrTimer_ms = 0; +#ifdef T41_USB_AUDIO +extern AudioPlayQueue Q_usbOut_L; +extern AudioPlayQueue Q_usbOut_R; +#endif + #define RXTXZoom 3 #define TXIQZOOM 3 @@ -718,6 +725,36 @@ void PlayBuffer(DataBlock *data){ } } +#ifdef T41_USB_AUDIO +static float32_t usbTmp[BUFFER_SIZE]; +static float32_t g_usbRxGain = 1.5f; // GAIN SET FOR WJST, DEFAULT IS 2 + +void PlayUsbBufferPreVol(DataBlock *data){ + for (unsigned i = 0; i < N_BLOCKS; i++) { + int16_t *pL = Q_usbOut_L.getBuffer(); + int16_t *pR = Q_usbOut_R.getBuffer(); + + // Copy one block then apply gain + arm_copy_f32(&data->I[BUFFER_SIZE * i], usbTmp, BUFFER_SIZE); + arm_scale_f32(usbTmp, g_usbRxGain, usbTmp, BUFFER_SIZE); + + // Optional: clip to [-1, +1] to avoid wrap distortion + for (size_t k = 0; k < BUFFER_SIZE; k++) { + if (usbTmp[k] > 1.0f) usbTmp[k] = 1.0f; + else if (usbTmp[k] < -1.0f) usbTmp[k] = -1.0f; + } + + arm_float_to_q15(usbTmp, pL, BUFFER_SIZE); + arm_float_to_q15(usbTmp, pR, BUFFER_SIZE); + + Q_usbOut_L.playBuffer(); + Q_usbOut_R.playBuffer(); + } +} +#endif + + + /** * Initialize the global variables to their default startup values * 1) Configure the RXfilters @@ -924,19 +961,21 @@ DataBlock * ReceiveProcessing(const char *fname){ // Interpolate InterpolateReceiveData(&data, &RXfilters); - // Volume adjust for audio volume setting. I and Q contain duplicate data, don't - // need to scale both + #if defined(T41_USB_AUDIO) && (defined(USB_AUDIO) || defined(USB_MIDI_AUDIO_SERIAL)) + // Send audio to PC *before* volume knob affects it + PlayUsbBufferPreVol(&data); + #endif + + // Speaker path volume knob AdjustVolume(&data, &RXfilters); SaveData(&data, 6); // used by the unit tests - // Play sound on the speaker + // Always feed the speaker output (works in both builds) PlayBuffer(&data); elapsed_micros_sum = elapsed_micros_sum + usec; elapsed_micros_idx_t++; - //Flag(0); - return &data; } @@ -965,7 +1004,34 @@ float32_t GetMicRRMS(void){ * @param data The data block to put the samples in * @return ESUCCESS if samples were read, EFAIL if insufficient samples are available */ -errno_t ReadMicrophoneBuffer(DataBlock *data){ +errno_t ReadMicrophoneBuffer(DataBlock *data) +{ + if (!data) return EFAIL; + +#ifdef T41_USB_AUDIO +if (GetFt8Mode()) { + const uint32_t outCount = N_BLOCKS_EX * BUFFER_SIZE; + + bool ok = Ft8UsbBridge_GetSamples(data->I, outCount); + if (!ok) { + memset(data->I, 0, outCount * sizeof(float32_t)); + } + + // Dual-mono + attenuation (prevents harshness/clipping/pulsing) + for (uint32_t i = 0; i < outCount; i++) { + float s = data->I[i] * 0.20f; // try 0.10–0.30 + data->I[i] = s; + data->Q[i] = s; + } + + data->N = outCount; + data->sampleRate_Hz = SR[SampleRate].rate; + return ESUCCESS; +} +#endif + + // ----- existing microphone code continues below ----- + // are there at least N_BLOCKS buffers in each channel available ? if ((uint32_t)Q_in_L_Ex.available() > N_BLOCKS_EX+0 && (uint32_t)Q_in_R_Ex.available() > N_BLOCKS_EX+0) { //counter++; diff --git a/code/src/PhoenixSketch/Ft8UsbBridge.cpp b/code/src/PhoenixSketch/Ft8UsbBridge.cpp new file mode 100644 index 0000000..dd8f753 --- /dev/null +++ b/code/src/PhoenixSketch/Ft8UsbBridge.cpp @@ -0,0 +1,226 @@ +// Ft8UsbBridge.cpp +// +// USB (WSJT-X, ~44.1 kHz) -> Phoenix SDR TX path (sampleRate, e.g. 192 kHz) +// Linear resampler with a float FIFO. +// +// This version adds a simple "warm-up" phase: +// - The first few calls to Ft8UsbBridge_GetSamples() return pure silence. +// - After that, we only send fully interpolated data plus a small +// "hold last sample" tail if we ever run short. +// Goal: avoid the ugly "slow motor" pulsing on the very first TUNE. + +#include +#include +#include "Ft8UsbBridge.h" +#include "Config.h" + +#if defined(T41_USB_AUDIO) && (defined(USB_AUDIO) || defined(USB_MIDI_AUDIO_SERIAL)) + +// ----------------------------------------------------------------------------- +// USB audio objects +// We use LEFT channel only from WSJT-X (WSJT usually sends same audio on L/R). +// ----------------------------------------------------------------------------- +AudioInputUSB g_usbIn; +AudioRecordQueue g_usbQueueL; +AudioRecordQueue g_usbQueueR; +AudioConnection g_pcUsbToQueueL(g_usbIn, 0, g_usbQueueL, 0); +AudioConnection g_pcUsbToQueueR(g_usbIn, 1, g_usbQueueR, 0); + +// ----------------------------------------------------------------------------- +// Internal SRC state +// ----------------------------------------------------------------------------- + +// SDR sample rate (e.g., 192000.0f). Set in Ft8UsbBridge_Init(). +static float g_sdrSampleRate = 192000.0f; + +// Teensy USB audio nominal sample rate. +// AUDIO_SAMPLE_RATE_EXACT ≈ 44117.64706 Hz. +static const float kUsbSampleRate = AUDIO_SAMPLE_RATE_EXACT; + +// FIFO for buffering USB-rate samples (float, mono). +// 4096 samples is ~90 ms at 44.1 kHz – plenty of cushion. +static float g_usbFifo[4096]; +static uint32_t g_fifoCount = 0; // number of valid samples in g_usbFifo[] +static float g_srcPhase = 0.0f; // fractional index in FIFO (in USB samples) + +// Track the last valid audio sample so we can avoid sudden zeros mid-stream. +static float g_lastSample = 0.0f; +static bool g_haveSample = false; + +// Simple "warm-up" counter: how many complete SDR blocks we've produced +// since Ft8UsbBridge_Init() was called. +static uint32_t g_blocksSinceInit = 0; + +// ----------------------------------------------------------------------------- +// FIFO helpers +// ----------------------------------------------------------------------------- + +// Compact FIFO after "consumed" samples have been used. +static void fifo_consume(uint32_t consumed) +{ + if (consumed == 0 || g_fifoCount == 0) return; + + if (consumed >= g_fifoCount) { + g_fifoCount = 0; + return; + } + + memmove(g_usbFifo, + g_usbFifo + consumed, + (g_fifoCount - consumed) * sizeof(float)); + + g_fifoCount -= consumed; +} + +// Pull as many 128-sample USB blocks as we can and append them to FIFO. +static void pump_usb_to_fifo() +{ + const uint32_t blockSamples = 128; + const uint32_t fifoCapacity = (uint32_t)(sizeof(g_usbFifo) / sizeof(g_usbFifo[0])); + + while (g_usbQueueL.available() > 0 && + g_usbQueueR.available() > 0 && + (g_fifoCount + blockSamples) <= fifoCapacity) + { + int16_t *pL = g_usbQueueL.readBuffer(); + int16_t *pR = g_usbQueueR.readBuffer(); + if (!pL || !pR) break; + + for (uint32_t i = 0; i < blockSamples; ++i) { + float l = (float)pL[i] / 32768.0f; + float r = (float)pR[i] / 32768.0f; + g_usbFifo[g_fifoCount + i] = 0.5f * (l + r); + } + + g_fifoCount += blockSamples; + g_usbQueueL.freeBuffer(); + g_usbQueueR.freeBuffer(); + } +} + + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +void Ft8UsbBridge_Init(float sdrSampleRateHz) +{ + g_sdrSampleRate = (sdrSampleRateHz > 0.0f) ? sdrSampleRateHz : 192000.0f; + + g_fifoCount = 0; + g_srcPhase = 0.0f; + g_lastSample = 0.0f; + g_haveSample = false; + g_blocksSinceInit = 0; + + // Optional but helpful: reset queues so we start clean + g_usbQueueL.end(); + g_usbQueueR.end(); + g_usbQueueL.begin(); + g_usbQueueR.begin(); +} + + +// Get "outCount" SDR-rate samples for the TX path. +// Caller (DSP.cpp) will: +// - Put this into data->I, +// - Zero data->Q, +// - Run the TX chain (or direct-to-IQ, depending on your variant). +// +// We always fill the whole buffer. On startup we deliberately output +// a few blocks of silence so the USB FIFO can "stabilize" before the +// rig ever sees actual RF drive. +bool Ft8UsbBridge_GetSamples(float *out, uint32_t outCount) +{ + if (!out || outCount == 0) { + return false; + } + + // -------------------------------------------------------------- + // 0) WARM-UP PHASE + // + // On the very first TUNE, USB audio may only have just started to + // arrive from WSJT-X. To avoid any chance of block-by-block + // "motorboating", we deliberately send a few blocks of *pure + // silence* before we ever send any real tone. + // + // At 192 kHz with 2048-sample blocks, each block is ~10.7 ms. + // 4 blocks ≈ 43 ms of silence – inaudible in an FT8 TX but enough + // to get a stable FIFO. + // -------------------------------------------------------------- + const uint32_t WARMUP_BLOCKS = 4; + + if (g_blocksSinceInit < WARMUP_BLOCKS) + { + // Keep draining USB queues so they don’t overflow while we “warm up” + pump_usb_to_fifo(); + + memset(out, 0, outCount * sizeof(float)); + g_blocksSinceInit++; + return true; + } + + + // Bring in fresh USB data. + pump_usb_to_fifo(); + + // How many input (USB) samples to advance per output (SDR) sample. + // E.g. for 44.1k -> 192k: step ≈ 0.2299 + const float step = kUsbSampleRate / g_sdrSampleRate; + + float phase = g_srcPhase; + uint32_t produced = 0; + + // -------------------------------------------------------------- + // 1) Normal interpolation from FIFO + // -------------------------------------------------------------- + while (produced < outCount && g_fifoCount > 1) { + uint32_t i0 = (uint32_t)phase; + + // Need two samples for interpolation. + if (i0 + 1 >= g_fifoCount) { + break; + } + + float frac = phase - (float)i0; + float s0 = g_usbFifo[i0]; + float s1 = g_usbFifo[i0 + 1]; + + float s = s0 + frac * (s1 - s0); // linear interpolation + + out[produced++] = s; + + // Remember last valid sample so we can avoid sharp transitions + // if the FIFO ever runs slightly short mid-block. + g_lastSample = s; + g_haveSample = true; + + phase += step; + } + + // How many whole input samples did we consume? + uint32_t consumed = (uint32_t)phase; + g_srcPhase = phase - (float)consumed; + + if (consumed > 0) { + fifo_consume(consumed); + } + + // -------------------------------------------------------------- + // 2) Tail fill: if we ever underrun after warm-up, fill the rest + // of the block with the last valid sample. For a continuous + // WSJT tone this should be rare and effectively inaudible. + // -------------------------------------------------------------- + if (produced < outCount) { + float fill = (g_haveSample ? g_lastSample : 0.0f); + + for (uint32_t i = produced; i < outCount; ++i) { + out[i] = fill; + } + } + + g_blocksSinceInit++; + return true; +} + +#endif \ No newline at end of file diff --git a/code/src/PhoenixSketch/Ft8UsbBridge.h b/code/src/PhoenixSketch/Ft8UsbBridge.h new file mode 100644 index 0000000..92a85a4 --- /dev/null +++ b/code/src/PhoenixSketch/Ft8UsbBridge.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +// Simple USB→SDR sample-rate converter for FT8 transmit. +// - Captures mono audio from WSJT via USB (AudioInputUSB) +// - Stores it in a FIFO at ~44.1 kHz +// - Resamples to the Phoenix SDR sample rate (sampleRate, e.g., 192 kHz) +// - Provides blocks of float samples for the FT8 TX path. + +/** + * Initialize the FT8 USB bridge. + * + * Must be called after AudioMemory() and after the audio system is + * initialized (e.g., from InitializeAudio()). + * + * @param sdrSampleRateHz Phoenix SDR's internal sample rate (e.g. 192000.0f) + */ +void Ft8UsbBridge_Init(float sdrSampleRateHz); + +/** + * Get a block of SDR-rate audio samples for FT8 transmit. + * + * @param out Pointer to output buffer (length = outCount floats) + * @param outCount Number of SDR-rate samples requested + * + * @return true if we produced a reasonably complete block, + * false if there was not enough USB audio yet. + * + * On failure, the caller should typically zero-fill the TX buffer + * (or just accept that this block will be silent). + */ +bool Ft8UsbBridge_GetSamples(float *out, uint32_t outCount); diff --git a/code/src/PhoenixSketch/Globals.cpp b/code/src/PhoenixSketch/Globals.cpp index 8a81a23..6a5f6be 100644 --- a/code/src/PhoenixSketch/Globals.cpp +++ b/code/src/PhoenixSketch/Globals.cpp @@ -489,7 +489,13 @@ time_t getTeensy3Time() { void setup(void){ Serial.begin(115200); - SerialUSB1.begin(38400); // For CAT control + +#ifndef T41_USB_AUDIO + // Non-USB-audio build: + // CAT on SerialUSB1 + SerialUSB1.begin(38400); +#endif + Serial.println("T41 SDT Setup"); // get TIME from real time clock with 3V backup battery diff --git a/code/src/PhoenixSketch/Loop.cpp b/code/src/PhoenixSketch/Loop.cpp index 6bd3b12..607e24d 100644 --- a/code/src/PhoenixSketch/Loop.cpp +++ b/code/src/PhoenixSketch/Loop.cpp @@ -85,6 +85,9 @@ */ #include "SDT.h" +#include "MainBoard_AudioIO.h" +#include "FrontPanel.h" + // FIFO buffer for interrupt events #define INTERRUPT_BUFFER_SIZE 16 @@ -1275,11 +1278,21 @@ void ConsumeInterrupt(void){ // Handle all the other non-encoder interrupts switch (interrupt){ + case (iBUTTON_PRESSED):{ int32_t button = GetButton(); + SetButton(-1); // clear latched button + + if (button == 16) { // SET FT8 MODE + bool newState = !GetFt8Mode(); + SetFt8Mode(newState); + break; + } + HandleButtonPress(button); break; } + case (iVFO_CHANGE):{ // The VFO has been updated. We might have selected a different active VFO, // we might have changed frequency. diff --git a/code/src/PhoenixSketch/MainBoard_AudioIO.cpp b/code/src/PhoenixSketch/MainBoard_AudioIO.cpp index cfbc434..8b23df1 100644 --- a/code/src/PhoenixSketch/MainBoard_AudioIO.cpp +++ b/code/src/PhoenixSketch/MainBoard_AudioIO.cpp @@ -52,6 +52,7 @@ */ #include "MainBoard_AudioIO.h" +#include "Ft8UsbBridge.h" /** * The transition from analog to digital and digital to analog are handled using a fork @@ -98,6 +99,7 @@ * to see what it is what you're trying to transmit. Probably don't need this anymore. */ + // Generated using this tool: https://www.pjrc.com/teensy/gui/index.html // GUItool: begin automatically generated code AudioInputI2SQuad i2s_quadIn; //xy=576.75,225 @@ -120,6 +122,16 @@ AudioMixer4 modeSelectOutL; //xy=1725.75,184 AudioMixer4 modeSelectOutExR; //xy=1727.75,103 AudioMixer4 modeSelectOutR; //xy=1732.75,291 AudioOutputI2SQuad i2s_quadOut; //xy=1969.75,138 + +#ifdef T41_USB_AUDIO +AudioPlayQueue Q_usbOut_L; +AudioPlayQueue Q_usbOut_R; +AudioOutputUSB usbOut; +// stereo gain blocks for the PC feed +AudioAmplifier usbRxGainL; +AudioAmplifier usbRxGainR; +#endif + AudioConnection patchCord1(i2s_quadIn, 0, modeSelectInExL, 0); AudioConnection patchCord2(i2s_quadIn, 1, modeSelectInExR, 0); AudioConnection patchCord3(i2s_quadIn, 2, modeSelectInL, 0); @@ -141,7 +153,17 @@ AudioConnection patchCord18(Q_out_L, 0, modeSelectOutL, 0); AudioConnection patchCord19(modeSelectOutExL, 0, i2s_quadOut, 0); AudioConnection patchCord20(modeSelectOutL, 0, i2s_quadOut, 2); AudioConnection patchCord21(modeSelectOutExR, 0, i2s_quadOut, 1); -AudioConnection patchCord22(modeSelectOutR, 0, i2s_quadOut, 3); +AudioConnection patchCord22(modeSelectOutR, 0, i2s_quadOut, 3); + +#ifdef T41_USB_AUDIO +AudioConnection patchUsbGainL(Q_usbOut_L, 0, usbRxGainL, 0); +AudioConnection patchUsbGainR(Q_usbOut_R, 0, usbRxGainR, 0); + +AudioConnection patchUsbL(usbRxGainL, 0, usbOut, 0); +AudioConnection patchUsbR(usbRxGainR, 0, usbOut, 1); +#endif + + AudioControlSGTL5000 pcm5102_mainBoard; //xy=874.75,449 // GUItool: end automatically generated code @@ -151,18 +173,20 @@ AudioControlSGTL5000_Extended sgtl5000_teensy; static ModeSm_StateId previousAudioIOState = ModeSm_StateId_ROOT; -/** - * Get the previous audio I/O state. - * - * Returns the ModeSm state that the audio routing was last configured for. - * Used to detect state changes and avoid unnecessary reconfiguration of the - * audio graph when the mode hasn't changed. - * - * @return The previous ModeSm_StateId that audio routing was configured for - */ -ModeSm_StateId GetAudioPreviousState(void){ - return previousAudioIOState; -} +// Always exist so other modules can link +static bool g_ft8Mode = false; + +#ifdef T41_USB_AUDIO +enum class TxAudioSource : uint8_t { MIC = 0, USB = 2 }; +static void SelectTxInputSource(void); + +// remember the user’s modulation so FT8 can restore it +static ModulationType g_savedModulation = USB; +static bool g_haveSavedModulation = false; + +static TxAudioSource g_prevTxAudioSource = TxAudioSource::MIC; +static TxAudioSource g_txAudioSource = TxAudioSource::MIC; // <-- IMPORTANT: MIC at boot +#endif /** * Select a single active channel on a 4-channel audio mixer. @@ -177,6 +201,7 @@ ModeSm_StateId GetAudioPreviousState(void){ * @param mixer Pointer to the AudioMixer4 object to configure * @param channel Channel number to enable (0-3), all others will be muted */ + void SelectMixerChannel(AudioMixer4 *mixer, uint8_t channel){ for (uint8_t k = 0; k < 4; k++){ if (k == channel) mixer->gain(k,1); @@ -184,6 +209,58 @@ void SelectMixerChannel(AudioMixer4 *mixer, uint8_t channel){ } } +#ifdef T41_USB_AUDIO +static void SelectTxInputSource(void) +{ + const uint8_t ch = static_cast(g_txAudioSource); + SelectMixerChannel(&modeSelectInExL, ch); + SelectMixerChannel(&modeSelectInExR, ch); +} +#endif + +void SetFt8Mode(bool enabled) +{ + g_ft8Mode = enabled; + +#ifdef T41_USB_AUDIO + // Select TX audio source + g_txAudioSource = enabled ? TxAudioSource::USB : TxAudioSource::MIC; + + if (enabled) { + // Save current modulation once, then force USB + if (!g_haveSavedModulation) { + g_savedModulation = ED.modulation[ED.activeVFO]; + g_haveSavedModulation = true; + } + ED.modulation[ED.activeVFO] = USB; + UpdateRFHardwareState(); + } else { + // Restore modulation when leaving FT8 + if (g_haveSavedModulation) { + ED.modulation[ED.activeVFO] = g_savedModulation; + g_haveSavedModulation = false; + UpdateRFHardwareState(); + } + } + + // If we're already in TX, apply immediately + if (modeSM.state_id == ModeSm_StateId_SSB_TRANSMIT) { + SelectTxInputSource(); + g_prevTxAudioSource = g_txAudioSource; + } +#else + (void)enabled; +#endif +} + + + +bool GetFt8Mode(void) +{ + return g_ft8Mode; +} + + /** * Mute all channels on a 4-channel audio mixer. * @@ -259,11 +336,21 @@ void UpdateTransmitAudioGain(void){ * @see ModeSm state machine for state transition logic * @see UpdateRFHardwareState() in RFBoard.cpp */ + void UpdateAudioIOState(void){ - if (modeSM.state_id == previousAudioIOState){ - // Already in this state, no need to change - return; +if (modeSM.state_id == previousAudioIOState){ + +#ifdef T41_USB_AUDIO + // If still in TX and the selected TX source changed, re-apply it. + if (modeSM.state_id == ModeSm_StateId_SSB_TRANSMIT && g_txAudioSource != g_prevTxAudioSource) { + SelectTxInputSource(); + g_prevTxAudioSource = g_txAudioSource; } +#endif + + return; +} + switch (modeSM.state_id){ case (ModeSm_StateId_CALIBRATE_OFFSET_SPACE): case (ModeSm_StateId_CALIBRATE_TX_IQ_SPACE): @@ -297,13 +384,36 @@ void UpdateAudioIOState(void){ Q_in_L.begin(); Q_in_R.begin(); // Microphone input starts - Q_in_L_Ex.begin(); + #ifdef T41_USB_AUDIO + if (GetFt8Mode()) { + // FT8 TX uses Ft8UsbBridge (not the mic record queues) + Q_in_L_Ex.end(); + Q_in_R_Ex.end(); + } else { + // Normal voice SSB TX uses microphone queues + Q_in_L_Ex.begin(); + Q_in_R_Ex.begin(); + } + #else + Q_in_L_Ex.begin(); Q_in_R_Ex.begin(); + #endif + sgtl5000_teensy.micGain(ED.currentMicGain); // Input is microphone - SelectMixerChannel(&modeSelectInExL,0); + #ifdef T41_USB_AUDIO + SelectTxInputSource(); // chooses MIC(0) or USB(2) + #else + SelectMixerChannel(&modeSelectInExL,0); // mic SelectMixerChannel(&modeSelectInExR,0); + #endif + + #ifdef T41_USB_AUDIO + g_prevTxAudioSource = g_txAudioSource; + #endif + + // Output is samples to RF transmit SelectMixerChannel(&modeSelectOutExL,0); SelectMixerChannel(&modeSelectOutExR,0); @@ -425,7 +535,10 @@ void UpdateAudioIOState(void){ * @see SR[] array in Config.h for supported sample rates */ void InitializeAudio(void){ + SetI2SFreq(SR[SampleRate].rate); + + // The sgtl5000_teensy is the controller for the Teensy Audio board. We use it to get // the microphone input for SSB, and the I/Q output for the exciter board. In other // words, it is used for the transmit path. @@ -433,6 +546,15 @@ void InitializeAudio(void){ sgtl5000_teensy.enable(); AudioMemory(500); AudioMemory_F32(10); + + + #ifdef T41_USB_AUDIO + Ft8UsbBridge_Init((float)SR[SampleRate].rate); + usbRxGainL.gain(2.0f); // try 1.0 to 6.0 FOR FT8 GAIN TO WINDOWS + usbRxGainR.gain(2.0f); + SetFt8Mode(false); + #endif + sgtl5000_teensy.inputSelect(AUDIO_INPUT_MIC); // set mic pre-amp gain to 40dB & audio gain to 12dB sgtl5000_teensy.micGain(10); // sets pre-amp and input gain to achieve 10dB of total gain sgtl5000_teensy.lineInLevel(0); // set ADC right and left channel volumes to 0dB diff --git a/code/src/PhoenixSketch/MainBoard_AudioIO.h b/code/src/PhoenixSketch/MainBoard_AudioIO.h index 95088ae..750c904 100644 --- a/code/src/PhoenixSketch/MainBoard_AudioIO.h +++ b/code/src/PhoenixSketch/MainBoard_AudioIO.h @@ -24,6 +24,10 @@ extern AudioSynthWaveformSine transmitIQcal_oscillator; */ int SetI2SFreq(int freq); +// FT8 / USB TX audio select +void SetFt8Mode(bool enabled); +bool GetFt8Mode(void); + /** * @brief Initialize dual-codec audio subsystem for Phoenix SDR * @note Configures SGTL5000 codecs for transmit path (Teensy Audio Board) and receive path (main board) diff --git a/code/src/PhoenixSketch/MainBoard_DisplayHome.cpp b/code/src/PhoenixSketch/MainBoard_DisplayHome.cpp index 24994ab..5424d18 100644 --- a/code/src/PhoenixSketch/MainBoard_DisplayHome.cpp +++ b/code/src/PhoenixSketch/MainBoard_DisplayHome.cpp @@ -21,6 +21,8 @@ #include #include "FreeSansBold24pt7b.h" #include "FreeSansBold18pt7b.h" +#include "MainBoard_AudioIO.h" + // External references to objects and variables defined in MainBoard_Display.cpp extern RA8875 tft; @@ -285,23 +287,32 @@ static int64_t oldCenterFreq = 0; static int32_t oldBand = -1; static ModeSm_StateId oldState = ModeSm_StateId_ROOT; static ModulationType oldModulation = DCF77; +static bool oldFt8Mode = false; /** * Render the frequency, band name, and modulation mode pane. */ void DrawFreqBandModPane(void) { + bool ft8 = false; + #ifdef T41_USB_AUDIO + ft8 = GetFt8Mode(); + #endif + if ((oldCenterFreq != ED.centerFreq_Hz[ED.activeVFO]) || (oldBand != ED.currentBand[ED.activeVFO]) || (oldState != modeSM.state_id) || - (oldModulation != ED.modulation[ED.activeVFO])){ + (oldModulation != ED.modulation[ED.activeVFO]) || + (oldFt8Mode != ft8)){ PaneFreqBandMod.stale = true; } + if (!PaneFreqBandMod.stale) return; oldCenterFreq = ED.centerFreq_Hz[ED.activeVFO]; oldBand = ED.currentBand[ED.activeVFO]; oldState = modeSM.state_id; oldModulation = ED.modulation[ED.activeVFO]; + oldFt8Mode = ft8; tft.setFontDefault(); tft.fillRect(PaneFreqBandMod.x0, PaneFreqBandMod.y0, PaneFreqBandMod.width, PaneFreqBandMod.height, RA8875_BLACK); @@ -336,7 +347,9 @@ void DrawFreqBandModPane(void) { tft.print("(LSB)"); break; case USB: - tft.print("(USB)"); + // Only label FT8 when FT8 mode is enabled + if (ft8) tft.print("(FT8)"); + else tft.print("(USB)"); break; case AM: tft.print("(AM)"); diff --git a/code/src/PhoenixSketch/SDT.h b/code/src/PhoenixSketch/SDT.h index 9d4198a..648240c 100644 --- a/code/src/PhoenixSketch/SDT.h +++ b/code/src/PhoenixSketch/SDT.h @@ -6,11 +6,17 @@ #include "Config.h" #define RIGNAME "T41-EP SDT" -#define VERSION "Phx V1.2" +#define VERSION "Phx V1.3" // WSJT VERSION #include "BuildInfo.h" -#define Debug(x) Serial.println(x) +// Debug logging: enable only when you are NOT using USB Serial for CAT (e.g., FT8) +#ifdef ENABLE_DEBUG_SERIAL + #define Debug(x) Serial.println(x) +#else + #define Debug(x) do {} while(0) +#endif + #include // Installed via Arduino library manager #include // https://github.com/chipaudette/OpenAudio_ArduinoLibrary