@@ -1155,11 +1155,19 @@ void App::stopSimulator() {
11551155
11561156 if (sim_thread_.joinable ()) sim_thread_.join ();
11571157
1158+ // Keep synchronous decode mode on Windows for startup/runtime stability.
1159+ #ifdef _WIN32
1160+ modem_.setSynchronousMode (true );
1161+ if (virtual_modem_) {
1162+ virtual_modem_->setSynchronousMode (true );
1163+ }
1164+ #else
11581165 // Restore async decode mode for real audio operation
11591166 modem_.setSynchronousMode (false );
11601167 if (virtual_modem_) {
11611168 virtual_modem_->setSynchronousMode (false );
11621169 }
1170+ #endif
11631171
11641172 // Clear buffers
11651173 {
@@ -1509,6 +1517,14 @@ void App::render() {
15091517 ultra::gui::startupTrace (" App" , " render-set-output-gain-exit" );
15101518 }
15111519
1520+ #ifdef _WIN32
1521+ // Keep decode in synchronous mode on fragile Win10 systems.
1522+ if (!simulation_enabled_ && !modem_.isSynchronousMode ()) {
1523+ guiLog (" Win startup guard: forcing modem synchronous RX mode" );
1524+ modem_.setSynchronousMode (true );
1525+ }
1526+ #endif
1527+
15121528 // Process captured RX audio in the main thread.
15131529 // Avoids feeding modem state directly from SDL callback threads.
15141530 pollRadioRx ();
@@ -1793,6 +1809,13 @@ bool App::startRadioRx() {
17931809 return true ;
17941810 }
17951811
1812+ #ifdef _WIN32
1813+ if (!modem_.isSynchronousMode ()) {
1814+ guiLog (" startRadioRx guard: enabling synchronous modem RX mode" );
1815+ modem_.setSynchronousMode (true );
1816+ }
1817+ #endif
1818+
17961819 std::string input_dev = getInputDeviceName ();
17971820 if (!audio_.openInput (input_dev)) {
17981821 guiLog (" startRadioRx: openInput failed for '%s'" ,
@@ -1811,6 +1834,9 @@ bool App::startRadioRx() {
18111834 return false ;
18121835 }
18131836 radio_rx_enabled_ = true ;
1837+ radio_rx_started_ms_ = SDL_GetTicks ();
1838+ radio_rx_warmup_logged_ = false ;
1839+ radio_rx_first_chunk_logged_ = false ;
18141840 guiLog (" startRadioRx: capture started on '%s'" ,
18151841 input_dev.empty () ? " Default" : input_dev.c_str ());
18161842 return true ;
@@ -1821,13 +1847,27 @@ void App::stopRadioRx() {
18211847 audio_.closeInput ();
18221848 audio_.setRxCallback (AudioEngine::RxCallback{});
18231849 radio_rx_enabled_ = false ;
1850+ radio_rx_started_ms_ = 0 ;
1851+ radio_rx_warmup_logged_ = false ;
1852+ radio_rx_first_chunk_logged_ = false ;
18241853}
18251854
18261855void App::pollRadioRx () {
18271856 if (!audio_initialized_ || simulation_enabled_ || !radio_rx_enabled_) {
18281857 return ;
18291858 }
18301859
1860+ uint32_t now_ms = SDL_GetTicks ();
1861+ if (radio_rx_started_ms_ > 0 && (now_ms - radio_rx_started_ms_) < 200 ) {
1862+ if (!radio_rx_warmup_logged_) {
1863+ guiLog (" pollRadioRx warm-up: delaying modem feed for 200ms after capture start" );
1864+ radio_rx_warmup_logged_ = true ;
1865+ }
1866+ // Drain a small amount during warm-up to avoid unbounded growth.
1867+ (void )audio_.getRxSamples (2048 );
1868+ return ;
1869+ }
1870+
18311871 // Bounded drain per frame to keep UI responsive while preventing RX backlog.
18321872 constexpr size_t kChunkSamples = 2048 ;
18331873 constexpr int kMaxChunksPerFrame = 8 ;
@@ -1837,8 +1877,18 @@ void App::pollRadioRx() {
18371877 break ;
18381878 }
18391879
1880+ if (!radio_rx_first_chunk_logged_) {
1881+ guiLog (" pollRadioRx: first RX chunk=%zu samples" , samples.size ());
1882+ }
18401883 modem_.feedAudio (samples);
1884+ if (!radio_rx_first_chunk_logged_) {
1885+ guiLog (" pollRadioRx: first RX chunk fed to modem" );
1886+ }
18411887 modem_.processRxBuffer ();
1888+ if (!radio_rx_first_chunk_logged_) {
1889+ guiLog (" pollRadioRx: first RX chunk modem processing complete" );
1890+ radio_rx_first_chunk_logged_ = true ;
1891+ }
18421892 if (waterfall_) {
18431893 waterfall_->addSamples (samples.data (), samples.size ());
18441894 }
0 commit comments