@@ -627,6 +627,10 @@ void Synth::processUIQueue(const clap_output_events_t *outq)
627627 doFullRefresh = false ;
628628 didRefresh = true ;
629629 }
630+ // Accumulate rescan flags across the whole drain so we issue one
631+ // requestParamRescan / request_callback at the end, regardless of how many
632+ // messages in this batch wanted a rescan.
633+ uint32_t pendingRescan{0 };
630634 auto uiM = mainToAudio.pop ();
631635 while (uiM.has_value ())
632636 {
@@ -762,17 +766,18 @@ void Synth::processUIQueue(const clap_output_events_t *outq)
762766 if (idx < numMacros && uiM->uiManagedPointer )
763767 {
764768 auto &buf = patch.macroNames [idx];
765- std::fill (buf.begin (), buf.end (), 0 );
766- strncpy (buf.data (), uiM->uiManagedPointer , buf.size () - 1 );
767- AudioToUIMsg out{AudioToUIMsg::SET_MACRO_NAME };
768- out.paramId = idx;
769- out.patchNamePointer = buf.data ();
770- audioToUi.push (out);
771-
772- // Refresh host param info so the renamed macro picks up its display name.
773- AudioToUIMsg rescan{AudioToUIMsg::DO_PARAM_RESCAN };
774- rescan.paramId = CLAP_PARAM_RESCAN_INFO ;
775- audioToUi.push (rescan);
769+ // Only act on actual changes — avoids gratuitous INFO rescans on
770+ // preset loads where macro names match what the host already knows.
771+ if (std::strncmp (buf.data (), uiM->uiManagedPointer , buf.size ()) != 0 )
772+ {
773+ std::fill (buf.begin (), buf.end (), 0 );
774+ strncpy (buf.data (), uiM->uiManagedPointer , buf.size () - 1 );
775+ AudioToUIMsg out{AudioToUIMsg::SET_MACRO_NAME };
776+ out.paramId = idx;
777+ out.patchNamePointer = buf.data ();
778+ audioToUi.push (out);
779+ pendingRescan |= RescanRequest::INFO ;
780+ }
776781 }
777782 }
778783 break ;
@@ -785,20 +790,15 @@ void Synth::processUIQueue(const clap_output_events_t *outq)
785790 case MainToAudioMsg::SEND_POST_LOAD :
786791 {
787792 postLoad ();
793+ // Preset load just rewrote every param value — let the host re-read.
794+ pendingRescan |= RescanRequest::VALUES ;
788795 }
789796 break ;
790797 case MainToAudioMsg::SEND_PREP_FOR_STREAM :
791798 {
792799 prepForStream ();
793800 }
794801 break ;
795- case MainToAudioMsg::SEND_REQUEST_RESCAN :
796- {
797- onMainRescanParams = true ;
798- audioToUi.push ({AudioToUIMsg::DO_PARAM_RESCAN });
799- clapHost->request_callback (clapHost);
800- }
801- break ;
802802 case MainToAudioMsg::EDITOR_ATTACH_DETATCH :
803803 {
804804 isEditorAttached = uiM->paramId ;
@@ -830,6 +830,10 @@ void Synth::processUIQueue(const clap_output_events_t *outq)
830830 }
831831 uiM = mainToAudio.pop ();
832832 }
833+
834+ // One coalesced rescan request per drain pass.
835+ if (pendingRescan)
836+ requestParamRescan (pendingRescan);
833837}
834838
835839void Synth::reapplyControlSettings ()
@@ -930,16 +934,26 @@ void Synth::resetSoloState()
930934
931935void Synth::onMainThread ()
932936{
933- bool ex{true }, re{false };
934- if (onMainRescanParams.compare_exchange_strong (ex, re))
935- {
936- auto pe = static_cast <const clap_host_params_t *>(
937- clapHost->get_extension (clapHost, CLAP_EXT_PARAMS ));
938- if (pe)
939- {
940- pe->rescan (clapHost, CLAP_PARAM_RESCAN_VALUES | CLAP_PARAM_RESCAN_TEXT );
941- }
942- }
937+ auto flags = onMainRescanFlags.exchange (0 , std::memory_order_acquire);
938+ if (flags == 0 || !clapHost)
939+ return ;
940+ auto pe =
941+ static_cast <const clap_host_params_t *>(clapHost->get_extension (clapHost, CLAP_EXT_PARAMS ));
942+ if (!pe)
943+ return ;
944+ // CLAP says INFO is mutually exclusive with VALUES; issue separately.
945+ if (flags & RescanRequest::VALUES )
946+ pe->rescan (clapHost, CLAP_PARAM_RESCAN_VALUES );
947+ if (flags & RescanRequest::INFO )
948+ pe->rescan (clapHost, CLAP_PARAM_RESCAN_INFO );
949+ }
950+
951+ void Synth::requestParamRescan (uint32_t flags)
952+ {
953+ if (flags == 0 || !clapHost)
954+ return ;
955+ onMainRescanFlags.fetch_or (flags, std::memory_order_release);
956+ clapHost->request_callback (clapHost);
943957}
944958
945959} // namespace baconpaul::six_sines
0 commit comments