@@ -2,6 +2,10 @@ package org.operatorfoundation.audiocoder
22
33import kotlinx.coroutines.*
44import kotlinx.coroutines.flow.*
5+ import org.operatorfoundation.audiocoder.WSPRTimingConstants.AUDIO_CHUNK_DURATION_MILLISECONDS
6+ import org.operatorfoundation.audiocoder.WSPRTimingConstants.AUDIO_COLLECTION_DURATION_MILLISECONDS
7+ import org.operatorfoundation.audiocoder.WSPRTimingConstants.AUDIO_COLLECTION_PAUSE_MILLISECONDS
8+ import org.operatorfoundation.audiocoder.WSPRTimingConstants.CYCLE_INFORMATION_UPDATE_INTERVAL_MILLISECONDS
59import org.operatorfoundation.audiocoder.models.WSPRCycleInformation
610import org.operatorfoundation.audiocoder.models.WSPRDecodeResult
711import org.operatorfoundation.audiocoder.models.WSPRStationConfiguration
@@ -73,7 +77,7 @@ class WSPRStation(
7377 val stationState: StateFlow <WSPRStationState > = _stationState .asStateFlow()
7478
7579 /* *
76- * Most recent decode results.
80+ * Most recent WSPR decode results.
7781 * Updated after each successful decode cycle with all detected signals.
7882 */
7983 private val _decodeResults = MutableStateFlow <List <WSPRDecodeResult >>(emptyList())
@@ -187,7 +191,8 @@ class WSPRStation(
187191 */
188192 suspend fun requestImmediateDecode (): Result <List <WSPRDecodeResult >>
189193 {
190- return try {
194+ return try
195+ {
191196 if (! timingCoordinator.isCurrentlyInValidDecodeWindow())
192197 {
193198 val nextWindowInfo = timingCoordinator.getTimeUntilNextDecodeWindow()
@@ -292,6 +297,71 @@ class WSPRStation(
292297 // Phase 2: Collect audio for the required duration
293298 _stationState .value = WSPRStationState .CollectingAudio
294299 val audioCollectionStartTime = System .currentTimeMillis()
300+
301+ while (System .currentTimeMillis() - audioCollectionStartTime < AUDIO_COLLECTION_DURATION_MILLISECONDS )
302+ {
303+ val audioChunk = audioSource.readAudioChunk(AUDIO_CHUNK_DURATION_MILLISECONDS )
304+ signalProcessor.addSamples(audioChunk)
305+
306+ // Brief pause to prevent excessive CPU usage
307+ delay(AUDIO_COLLECTION_PAUSE_MILLISECONDS )
308+ }
309+
310+ // Phase 3: Process collected audio through WSPR decoder
311+ _stationState .value = WSPRStationState .ProcessingAudio
312+
313+ val nativeDecodeResults = signalProcessor.decodeBufferedWSPR(
314+ dialFrequencyMHz = configuration.operatingFrequencyMHz,
315+ useLowerSideband = configuration.useLowerSidebandMode,
316+ useTimeAlignment = configuration.useTimeAlignedDecoding
317+ )
318+
319+ // Phase 4: Convert and store results
320+ val processedResults = convertNativeResultsToApplicationFormat(nativeDecodeResults)
321+ _decodeResults .value = processedResults
322+
323+ return processedResults
324+ }
325+
326+ /* *
327+ * Converts native WSPR decoder results to application-friendly format.
328+ *
329+ * The native decoder returns WSPRMessage objects with specific field formats.
330+ * This method normalizes the data and adds application specific metadata.
331+ *
332+ * @param nativeResults Raw results from the native WSPR decoder
333+ * @return List of processed decode results with consistent formatting
334+ */
335+ private fun convertNativeResultsToApplicationFormat (nativeResults : Array <WSPRMessage >? ): List <WSPRDecodeResult >
336+ {
337+ if (nativeResults == null ) return emptyList()
338+
339+ return nativeResults.map { nativeMessage ->
340+ WSPRDecodeResult (
341+ callsign = nativeMessage.call?.trim() ? : WSPRDecodeResult .UNKNOWN_CALLSIGN ,
342+ gridSquare = nativeMessage.loc?.trim() ? : WSPRDecodeResult .UNKNOWN_GRID_SQUARE ,
343+ powerLevelDbm = nativeMessage.power,
344+ signalToNoiseRatioDb = nativeMessage.snr,
345+ frequencyOffsetHz = nativeMessage.freq,
346+ completeMessage = nativeMessage.message?.trim() ? : WSPRDecodeResult .EMPTY_MESSAGE ,
347+ decodeTimestamp = System .currentTimeMillis()
348+ )
349+ }
350+ }
351+
352+ /* *
353+ * Starts background updates for cycle information display.
354+ * Updates cycle position and timing information every second for UI consumption.
355+ */
356+ private fun startCycleInformationUpdates ()
357+ {
358+ CoroutineScope (Dispatchers .IO ).launch {
359+ while (stationOperationJob?.isActive == true )
360+ {
361+ _cycleInformation .value = timingCoordinator.getCurrentCycleInformation()
362+ delay(CYCLE_INFORMATION_UPDATE_INTERVAL_MILLISECONDS )
363+ }
364+ }
295365 }
296366
297367}
0 commit comments