On macOS wxGraphicsContext::DrawBitmap(wxBitmap) is far more expensive than the
wxGraphicsBitmap overload: wxBitmapRefData::GetImage() caches nothing, so every
call allocates a fresh NSImage and draws through -[NSImage drawInRect:], where
the wxGraphicsBitmap path goes straight to CGContextDrawImage. The waterfall made
one such call per pixel block -- m_imgHeight/dy of them, ~200 on a 600px tall
plot -- on each of its 10 frames a second.
* Waterfall blocks are now held as {wxBitmap, wxGraphicsBitmap} pairs. The
wxBitmap stays as the StretchBlit target; the renderer-native copy is made once,
when the block is filled, rather than on every frame it spends scrolling down
the screen. Measured 1.648 -> 0.453 ms per paint at 700x600.
* PlotScalar composites its plot area the same way. The conversion is per frame
here since plotArea_ is redrawn each time, but it still beats the NSImage round
trip: 0.381 -> 0.205 ms per paint at 700x200 on the data-only repaint path that
"Frm Mic" and friends actually take.
* Waterfall graticule labels are laid out once per resize instead of per frame.
wxWindowMac::DoGetTextExtent builds and destroys a wxGraphicsContext per call
and drawGraticule() was making ~25 of them a frame for text that only moves when
the control is resized. It also measured every one second tick before checking
whether that tick gets a label, so most of the measuring was discarded.
* Dropped BeginLayer(1.0)/EndLayer from the waterfall, scalar and spectrum plots.
At opacity 1.0 the transparency layer allocates and composites an offscreen
buffer to produce exactly what drawing directly produces.
Separately, the heatmap LUT was filled over 0..254 while plotPixelData() clamps
intensity to 255 and reaches it whenever a bin sits at the top of the current
range -- so the hottest pixels took their colour from indeterminate memory on
essentially every frame carrying signal. Rendering a synthetic spectrum offscreen,
1665 pixels in the peak column came out as RGB(15,0,0), RGB(62,0,0) and
RGB(220,0,0) instead of the intended full-scale red.
Verified by driving draw() offscreen and diffing the output against master over
400 frames, enough to fill the waterfall and exercise block recycling roughly 200
times: the performance changes alone are bit-for-bit identical, and the only
pixels the LUT fix moves are the max-intensity ones described above.
test_rade_loss.sh passes with these changes, though it cannot really speak to
them: it exercises no plotting, and on this machine it fails intermittently on
master too (5 pass / 1 fail on master, 4 pass / 3 fail here across interleaved
runs of the two builds).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADMCssMazpW1igtvMHXNWZ
* Equalizer: make sure number of output samples is correct.
There may be a scenario where `EqualizerStep` doesn't properly set the number of output samples. This ensures that we're always setting the number of output samples to a valid value.
* Add PR #1480 to changelog.
* Fix compiler error with Hamlib 5.0.
* Fix typo.
* Bring back old code for Ubuntu 22.04.
* Update CMake to explicitly look for rig_state.h.
* Add PR #1477 to changelog.
* Fix typo.
* Remove deprecated macos-14, test only on macos-26 due to GitHub issues.
* Work around dyld error when enabling sanitizers.
* Readd macOS 14 and 15 to tests.
* Add PR #1478 to changelog.
* Fix waterfall/spectrum filling solid colour during half-duplex TX
The half-duplex-TX "blank the display" path memset() the spectrum
arrays to zero bytes, but this display's dB scale runs 0 (loudest) to
MIN_MAG_DB (quietest) -- zero meant "loudest possible", not silence,
so it painted the waterfall solid instead of blanking it (yellow,
occasionally clamping to red at the top of the colour scale).
Filling with MIN_MAG_DB alone wasn't sufficient either: the
waterfall's colour scale is relative to each row's own peak (peak
minus a fixed 20dB window), so a perfectly flat row -- regardless of
its absolute level -- always reads as "loud relative to itself". This
showed up live as a green-to-red fade as the auto-ranging baseline
(m_max_mag) caught up to the new flat level.
Fixed properly by detecting a row where every bin sits exactly at the
floor (real spectra essentially never do) and rendering it as plain
black directly, bypassing the relative intensity scale and leaving
the auto-ranging baseline untouched, rather than running synthetic
placeholder data through logic designed for real spectra.
Confirmed identical on master and v3.0-dev; tested live against a
real half-duplex TX/RX cycle.
* Add PR #1462 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Fix issue preventing TX thread from sleeping.
* Forgot additional change.
* Try 40ms frames.
* 10ms
* Probably still need to feed zeroes as needed.
* Revert frame duration back to 20ms.
* Add PR #1465 to changelog.
* Enable CCache to make CI builds run more quickly.
* Ensure Hamlib is also part of ccache.
* Try creating symlink.
* Increase ccache max size limit.
* Add ccache for Windows builds.
* Add cache for lint as well.
* Make sure PGO is covered by ccache.
* Dummy change to make sure build is cached.
* macOS: Ensure node 24 is used for ccache
* Windows: use node 24.
* Increase max cache size on macOS to 5G.
* Add Windows debugging for ccache.
* Use CMake compiler launcher to launch ccache.
* Use symlinks for PGO builds.
* Don't cache PGO use steps, we can't afford to with only 10GB available.
* Fix issue causing cache misses.
* Forgot that we need to use single quotes.
* Revert previous whitespace change.
* Enable ccache for Linux builds.
* Fix shell command error.
* Add PR #1328 to changelog.
* Fix FreeDV Reporter column-order repair to detect gaps, not just trailing entries
If a persisted freedvReporterColumnOrder value was missing an index
from the middle of its range (not just missing new indices appended
after a NUM_COLS increase), the old repair logic never noticed --
it only ever appended indices above the current max element. A
column permanently missing from the list means the wxDataViewCtrl
never creates it, so any later getColumnForModelColId_() lookup for
that column asserts/crashes (e.g. while sorting on it as new spots
arrive).
Reproduced and fixed against a real corrupted config
(ColumnOrder missing index 12/SNR_COL from the middle, confirmed to
crash on assert `item != nullptr` in getColumnForModelColId_ during
live FreeDV Reporter use) -- also explains a previously-unexplained
symptom of an extra empty column appearing when dragging the
rightmost column divider.
* Fix column-order gap detection, sentinel-column drag corruption, and Last TX width
Brings this branch's earlier column-order fix up to the same final
state as testing surfaced on v3.0-dev, since all three are general
bugs unrelated to the LAST_RX_MODE_COL difference between the two
branches:
- Validate/repair the persisted column order unconditionally rather
than only when its length looks wrong -- a config value can have
the right length while still containing an out-of-range/duplicate
entry and missing a genuine one, which the old size()-only check
never caught.
- OnColumnReordered's save path assumed the trailing sentinel spacer
column always stays in the last visual position; nothing enforces
that, so a drag could leave it captured as if it were one of the
real NUM_COLS columns, silently dropping a real one and persisting
the sentinel's invalid model ID instead -- the actual cause of an
"empty column" appearing after reordering.
- Keep the Last TX column at least as wide as Last Update's actual
current width (both use the same date/time format), checked
periodically via the dialog's existing timer rather than once at
construction time, since neither column's real autosized width is
known reliably that early.
Confirmed working live on the desktop against a real corrupted
config with a real radio.
* Minor style tweak.
* Add PR #1458 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Enable PGO for Windows builds.
* instrumented prefix wasn't actually being added.
* Port generate_pgo_profiles.sh to PowerShell (thanks Claude).
* Try disabling RX so we can at least debug rest of pipeline.
* Fix profraw path.
* Try enabling RX code again.
* See if we can get some extra debugging.
* Bound process wait.
* Use Reporting sox invocation.
* Redo config file prior to RX run.
* Fix LLVM profile merge error.
* Add PR #1457 to changelog.
* Experiment: Enable LTO/PGO for macOS .app build.
* PGO: Fix tab layout to match new encoding.
* Try disabling LTO during instrumented build.
* Revert "Try disabling LTO during instrumented build."
This reverts commit e6b4e1a191.
* Try disabling temporal profiles.
* Need to run profiling on both x86_64 and arm64.
* Disable universal builds for the PGO instrumented build.
* Revert "Try disabling temporal profiles."
This reverts commit 89036a3ed6.
* Add PR #1456 to changelog.
* Fake change to force rebuild.
* Revert "Fake change to force rebuild."
This reverts commit a2a8886377.
* Add Intel checks for .app.
* No torch on Intel anymore.
* Fix crash on repeat main-window close while shutdown is in progress
topFrame_OnClose() unconditionally dereferenced m_reporterDialog, but a
second wxEVT_CLOSE_WINDOW arriving while the async RX/PTT shutdown from
a first close is still running (e.g. during a slow Hamlib rig disconnect
against an unresponsive radio) re-enters the handler after
m_reporterDialog has already been set to null on the first pass --
SIGSEGV in wxWindowBase::GetPosition(). Guard re-entry via terminating_
and null-check m_reporterDialog, matching the pattern already used in
the destructor and setConfiguration_().
* Don't block on rig disconnect against an unresponsive radio when turning modem off
Dropping the last shared_ptr reference to a rig controller runs its
destructor, which blocks until the rig actually finishes disconnecting.
Against an unresponsive radio (e.g. powered off, connected via rigctld)
this can take far longer than Hamlib's own client-side timeout/retry
settings suggest, since those don't bound however long rigctld itself
waits on the physical radio -- observed over a minute with no feedback
to the user. Move the last reference onto a detached thread for both
rig controllers so that wait can't hold up turning the modem off or
app shutdown.
* Bound the wait for rig-disconnect threads during app-close shutdown
The detached rig PTT/frequency controller disconnect threads added in
the previous commit have no upper bound on app-close: MainFrame::Destroy()
fires right after performFreeDVOff_() returns, with nothing joining those
threads, so an unresponsive rig still mid-disconnect when the process
actually exits gets its thread killed outright -- e.g. a queued ptt(false)
might never reach the radio. Have each detached thread signal completion
via a future, and on the terminating path wait up to 3s (shared deadline
across both) before proceeding to Destroy() -- long enough for a merely
slow rig to finish cleanly, still bounded so a truly unresponsive one
can't hang shutdown.
Also expand the terminating_ guard comment in topFrame_OnClose() to note
it prevents more than the m_reporterDialog crash: m_RxRunning stays true
until deep inside the same async shutdown, so a repeat close request
could otherwise re-enter OnTogBtnOnOff() a second time concurrently with
the shutdown already in progress.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* Add PR #1452 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Fix CMAKE_REQUIRED_DEFINITIONS missing -D prefix in GTK3 wx probe (#1449)
wxWidgets_DEFINITIONS is a list of bare macro tokens (correct for the
COMPILE_DEFINITIONS property UsewxWidgets.cmake sets it via), but
CMAKE_REQUIRED_DEFINITIONS requires full "-DFOO" strings. Without the
prefix, check_cxx_symbol_exists()'s probe compile passes each bare
token as a raw command-line argument, which the compiler treats as a
(nonexistent) input filename and errors out on -- silently failing the
whole check closed (WX_BUILT_FOR_GTK3 always false) even against a
genuinely GTK3-built wxWidgets.
With HAS_GTK3 then never defined, topFrame.cpp's GTK3 guard takes the
non-GTK3 fallback branch and creates a stray 1x1 reference wxWindow
directly on the main frame -- disabling wx's "auto-resize sole child
to fill the frame" behaviour and freezing the entire main window's
layout on resize.
Verified on a real GTK3 wxWidgets 3.3.1 build (Mageia 10): reproduced
the false negative on a completely fresh configure (no stale cache
involved), then confirmed the fix resolves it on the first try.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add PR #1450 to changelog.
* Revert accidental commit.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Fix stale selection index crash in wxListViewComboPopup
OnMouseMove() deselected the previously hot-tracked row (m_value)
without checking it was still a valid index, so any external
DeleteAllItems() on the underlying list left a stale index that
crashed the next mouse-move over the popup.
* Fix heard-stations callsign combo stuck-highlight and right-click behaviour
OnCloseCallsignList/OnRightClickCallsignList moved keyboard focus away
from the read-only combo synchronously, but the popup window overlaps
the combo's own value area while open and GTK doesn't always repaint
that region on dismiss, leaving it showing a stale "focused/selected"
highlight. Defer the focus change via CallAfter and force a repaint
once the popup's dismissal has fully finished.
Also stop OnRightClickCallsignList from clearing the displayed
callsign text on deselect -- it should only remove the selection,
consistent with how right-click deselect works in the FreeDV Reporter
list.
* Add PR #1448 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Require C++20 support when building FreeDV.
* Fix Windows compiler errors.
* Add PR #1440 to changelog.
* Fix compiler errors due to wxWidgets 3.0 and C++20.
* Fix Voice Keyer/PTT context menu positioning under native Wayland
The manual left-offset used to keep these menus on-screen on X11
(added 2023, commit 98c43d83) doesn't translate to Wayland's
surface-relative xdg_popup anchor model -- it was landing at the
toplevel's origin instead of next to the button. Added
LeftOffsetContextMenuPosition() which detects a Wayland GDK display at
runtime and returns wxDefaultPosition there, letting GTK's own popup
placement handle screen-edge avoidance (as already happens for the
Reporter Send/Clear menus, which never used a manual offset). X11
keeps the original offset unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add PR #1438 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Defer Voice Keyer right-click menu popup via CallAfter
On GTK, PopupMenu() called synchronously from the wxEVT_CONTEXT_MENU
handler opens the menu while the originating right-click's implicit
pointer grab is still active, so the matching button-up is delivered
straight to the menu, selecting whatever is under the cursor before it
can be read. Deferring via CallAfter lets the initiating click fully
complete first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix Voice Keyer/PTT right-click menu using established RIGHT_UP workaround
The CallAfter approach in the previous commit didn't help: on a fast
click, GDK often has both the button-press and its paired release
already queued and dispatches both before any idle-priority callback
runs, so the menu still opened with the click already spent.
This file already has the correct fix for the identical issue on other
widgets (m_txLevelBox, m_txtTxLevelNum, m_btnTogTune, added earlier):
on wxGTK 3.3+, wxEVT_CONTEXT_MENU is generated from button-press, so
showing a menu from it opens while that click's release is still
pending and GTK delivers the release straight to the menu as a
selection. Bind wxEVT_RIGHT_UP instead for wx>=3.3 (keeping
wxEVT_CONTEXT_MENU on older wx, matching the existing widgets), which
only fires once the click has genuinely finished.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix FreeDV Reporter Send/Clear button right-click menus (same RIGHT_UP fix)
Same class of bug as the Voice Keyer/PTT buttons: wxEVT_CONTEXT_MENU is
generated from button-press on wxGTK 3.3+, so the menu opens while the
click's release is still pending and GTK delivers it straight to the
menu as a selection. Was only visible when the item under the cursor
happened to be enabled -- a disabled item just closes the menu
silently on release, masking the bug.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Scope the RIGHT_UP context-menu workaround to GTK only
The wxEVT_CONTEXT_MENU-on-button-press behaviour this works around is
GTK-specific: MSW already generates the event on button-up, and OSX
uses ctrl-click. The version-only guard (wxCHECK_VERSION(3,3,0)) would
otherwise apply the same override on Windows/Mac builds using wx 3.3+,
which could drop keyboard-invoked (Menu key/Shift+F10) context menus
on MSW since that only comes through wxEVT_CONTEXT_MENU. Add
defined(__WXGTK__) to all six guard sites, including the pre-existing
one for m_txLevelBox/m_txtTxLevelNum/m_btnTogTune this pattern was
copied from.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Drop version gate: dismiss-on-release bug confirmed present on wx 3.2 too
Direct testing on a wxGTK 3.2 build showed the same context-menu
dismiss-on-release bug the wxCHECK_VERSION(3,3,0) guard assumed was
3.3-specific (copied from the pre-existing txLevel/tune pattern this
was based on). Reading wx 3.2 vs 3.3 source confirmed the press-side
event synthesis is identical in both versions, so the fix now applies
on all wxGTK versions for these real windowed button widgets. The
original txLevel/tune block's version gate is left untouched since its
stated rationale (windowless widgets not generating RIGHT_UP pre-3.3)
is a separate, more specific concern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Add PR #1437 to changelog.
---------
Co-authored-by: Barry Jackson <barjac@mageia.org>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mooneer Salem <mooneer@gmail.com>
* Harden experimental tab layout persistence
Fix three defect classes found in the AUI tab-layout save/restore feature
(still gated behind Tools->Options->Debugging->Enable Experimental Features):
- SavePerspective()/LoadPerspective() (topFrame.cpp) keyed each saved tab by
its positional AddPage() index, so any future reordering/insertion/removal
of a tab silently corrupted previously-saved layouts (this already caused
a shipped hang, fixed ad hoc in a7aab5d7 when the SNR plot was added). Tabs
are now keyed by their (stable, unique) caption text instead.
- LoadPerspective() had an assert(dest_tabs != nullptr) that compiles out in
release builds (NDEBUG), so a saved layout that parses to zero tab groups
would null-deref instead of failing safely. It now falls back to a single
default tab group holding every page.
- togglePTT() and OnTogBtnVoiceKeyerClick() each guessed which tab to restore
by scanning a hardcoded list of 5 plot windows for one sharing "Frm Mic"'s
tab group, because wxAuiNotebook::GetSelection() is ambiguous once tabs are
split into multiple simultaneously-visible groups. Replaced both copies
with a shared captureCurrentMicGroupTab_() helper that asks the specific
tab group directly (wxAuiTabCtrl::GetActivePage()) instead of guessing.
The Experimental Features gate itself is unchanged; this only hardens the
feature while it's still under that flag.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix tab layout being clobbered by off-then-on Experimental Features toggle
Both the load (main.cpp, in loadConfiguration_()) and save (exportConfiguration_())
of the AUI tab layout were independently gated on the live experimentalFeatures
value. LoadPerspective() only ever runs once, at startup, so toggling the checkbox
mid-session has no effect on the current layout. But the exit-time save re-read the
live (possibly since-toggled) flag, so: start with the flag off (load skipped, tabs
render as one flat group), flip it back on, then exit -> the flat layout gets saved
right over a previously-good split layout.
Cache the flag value at the point the load decision is made
(tabLayoutPersistenceEnabledAtStartup_) and use that same cached value to gate the
exit-time save, instead of the live value.
Found via live testing of the previous commit's fixes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix assertion crash dragging a tab near a group left empty by an old saved layout
LoadPerspective() created a live tab-group pane for every '|'-delimited entry in
the saved layout string before this commit's caption fix, even when every one of
that group's saved tokens failed to resolve to a current tab (as happens for any
pre-existing saved layout from before tabs were keyed by caption instead of
position - see 49ec38c1). The result was a zero-page wxAuiTabCtrl left sitting in
the AUI layout. wxAuiNotebook's own drag-and-drop handling doesn't tolerate that:
dragging a tab near the empty group crashed with
"./src/aui/auibook.cpp(371): assert "idx < m_pages.GetCount()" failed in
GetPage(): Invalid Page index" (reported from live testing on the desktop, which
still had an old-format two-group saved layout).
Track the last group known to hold at least one page separately from the group
just created each loop iteration, and sweep up any that ended up empty via
RemoveEmptyTabFrames() (the same cleanup the function already uses for the
previous session's tabs) instead of leaving them in the layout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Update changelog for drag-and-drop crash fix
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>