Origin: upstream, commit:39305c9bc214795c7933024d4082105c4600c962
Author: Dmitri Ovodok <dmitrio95@yandex.ru>
Description: fix #280493: fix audio glitches on note input and playback on MacOS
 Ensure that realtime-context routines in sequencer do not access
 QSettings directly and use cached preferences for faster access.
 The exception is a hack with fake JACK transport on count-in: it
 seems to require working with QSettings-based preferences directly.
 .
 This improves general performance of playback so audible glitches
 which were reported for MacOS seem to not appear with this patch.

--- a/mscore/musescore.cpp
+++ b/mscore/musescore.cpp
@@ -541,6 +541,9 @@ void MuseScore::preferencesChanged(bool
       newWizard = 0;
       reloadInstrumentTemplates();
       updateInstrumentDialog();
+
+      if (seq)
+            seq->preferencesChanged();
       }
 
 //---------------------------------------------------------
--- a/mscore/seq.cpp
+++ b/mscore/seq.cpp
@@ -235,6 +235,41 @@ void Seq::setScoreView(ScoreView* v)
       }
 
 //---------------------------------------------------------
+//   Seq::CachedPreferences::update
+//---------------------------------------------------------
+
+void Seq::CachedPreferences::update()
+      {
+      portMidiOutputLatencyMilliseconds = preferences.getInt(PREF_IO_PORTMIDI_OUTPUTLATENCYMILLISECONDS);
+      jackTimeBaseMaster = preferences.getBool(PREF_IO_JACK_TIMEBASEMASTER);
+      useJackTransport = preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT);
+      useJackMidi = preferences.getBool(PREF_IO_JACK_USEJACKMIDI);
+      useJackAudio = preferences.getBool(PREF_IO_JACK_USEJACKAUDIO);
+      useAlsaAudio = preferences.getBool(PREF_IO_ALSA_USEALSAAUDIO);
+      usePortAudio = preferences.getBool(PREF_IO_PORTAUDIO_USEPORTAUDIO);
+      usePulseAudio = preferences.getBool(PREF_IO_PULSEAUDIO_USEPULSEAUDIO);
+      }
+
+//---------------------------------------------------------
+//   startTransport
+//---------------------------------------------------------
+
+void Seq::startTransport()
+      {
+      cachedPrefs.update();
+      _driver->startTransport();
+      }
+
+//---------------------------------------------------------
+//   stopTransport
+//---------------------------------------------------------
+
+void Seq::stopTransport()
+      {
+      _driver->stopTransport();
+      }
+
+//---------------------------------------------------------
 //   init
 //    return false on error
 //---------------------------------------------------------
@@ -246,6 +281,7 @@ bool Seq::init(bool hotPlug)
             running = false;
             return false;
             }
+      cachedPrefs.update();
       running = true;
       return true;
       }
@@ -332,7 +368,7 @@ void Seq::start()
             useJackTransportSavedFlag    = true;
             preferences.setPreference(PREF_IO_JACK_USEJACKTRANSPORT, false);
             }
-      _driver->startTransport();
+      startTransport();
       }
 
 //---------------------------------------------------------
@@ -355,7 +391,7 @@ void Seq::stop()
       if (!_driver)
             return;
       if (!preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT) || (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT) && _driver->getState() == Transport::PLAY))
-            _driver->stopTransport();
+            stopTransport();
       if (cv)
             cv->setCursorOn(false);
       if (midiRenderFuture.isRunning())
@@ -567,7 +603,7 @@ void Seq::processMessages()
                               int utick = cs->utime2utick(qreal(playFrame) / qreal(MScore::sampleRate));
                               cs->tempomap()->setRelTempo(msg.realVal);
                               playFrame = cs->utick2utime(utick) * MScore::sampleRate;
-                              if (preferences.getBool(PREF_IO_JACK_TIMEBASEMASTER) && preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT))
+                              if (cachedPrefs.jackTimeBaseMaster && cachedPrefs.useJackTransport)
                                     _driver->seekTransport(utick + 2 * cs->utime2utick(qreal((_driver->bufferSize()) + 1) / qreal(MScore::sampleRate)));
                               }
                         else
@@ -680,7 +716,7 @@ void Seq::process(unsigned framesPerPeri
       if (driverState != state) {
             // Got a message from JACK Transport panel: Play
             if (state == Transport::STOP && driverState == Transport::PLAY) {
-                  if ((preferences.getBool(PREF_IO_JACK_USEJACKMIDI) || preferences.getBool(PREF_IO_JACK_USEJACKAUDIO)) && !getAction("play")->isChecked()) {
+                  if ((cachedPrefs.useJackMidi || cachedPrefs.useJackAudio) && !getAction("play")->isChecked()) {
                         // Do not play while editing elements
                         if (mscore->state() != STATE_NORMAL || !isRunning() || !canStart())
                               return;
@@ -689,18 +725,18 @@ void Seq::process(unsigned framesPerPeri
 
                         // If we just launch MuseScore and press "Play" on JACK Transport with time 0:00
                         // MuseScore doesn't seek to 0 and guiPos is uninitialized, so let's make it manually
-                        if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT) && getCurTick() == 0)
+                        if (cachedPrefs.useJackTransport && getCurTick() == 0)
                               seekRT(0);
 
                         // Switching to fake transport while playing count in
                         // to prevent playing in other applications with our ticks simultaneously
-                        if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT) && mscore->countIn()) {
+                        if (cachedPrefs.useJackTransport && mscore->countIn()) {
                               // Stopping real JACK Transport
-                              _driver->stopTransport();
+                              stopTransport();
                               // Starting fake transport
                               useJackTransportSavedFlag = preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT);
                               preferences.setPreference(PREF_IO_JACK_USEJACKTRANSPORT, false);
-                              _driver->startTransport();
+                              startTransport();
                               }
                         }
                   // Initializing instruments every time we start playback.
@@ -801,7 +837,7 @@ void Seq::process(unsigned framesPerPeri
                                     if (playPosUTick >= loopOutUTick || cs->repeatList().utick2tick(playPosUTick) < cs->loopInTick().ticks()) {
                                           qDebug ("Process: playPosUTick = %d, cs->loopInTick().ticks() = %d, cs->loopOutTick().ticks() = %d, getCurTick() = %d, loopOutUTick = %d, playFrame = %d",
                                                             playPosUTick,      cs->loopInTick().ticks(),      cs->loopOutTick().ticks(),      getCurTick(),      loopOutUTick,    *pPlayFrame);
-                                          if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT)) {
+                                          if (cachedPrefs.useJackTransport) {
                                                 int loopInUTick = cs->repeatList().tick2utick(cs->loopInTick().ticks());
                                                 _driver->seekTransport(loopInUTick);
                                                 if (loopInUTick != 0) {
@@ -890,14 +926,14 @@ void Seq::process(unsigned framesPerPeri
                         // Connecting to JACK Transport if MuseScore was temporarily disconnected from it
                         if (useJackTransportSavedFlag) {
                               // Stopping fake driver
-                              _driver->stopTransport();
+                              stopTransport();
                               preferences.setPreference(PREF_IO_JACK_USEJACKTRANSPORT, true);
                               // Starting the real JACK Transport. All applications play in sync now
-                              _driver->startTransport();
+                              startTransport();
                               }
                         }
                   else
-                        _driver->stopTransport();
+                        stopTransport();
                   }
             }
       else {
@@ -952,7 +988,7 @@ void Seq::process(unsigned framesPerPeri
 void Seq::initInstruments(bool realTime)
       {
       // Add midi out ports if necessary
-      if (cs && (preferences.getBool(PREF_IO_JACK_USEJACKMIDI) || preferences.getBool(PREF_IO_ALSA_USEALSAAUDIO))) {
+      if (cs && (cachedPrefs.useJackMidi || cachedPrefs.useAlsaAudio)) {
             // Increase the maximum number of midi ports if user adds staves/instruments
             int scoreMaxMidiPort = cs->masterScore()->midiPortCount();
             if (maxMidiOutPort < scoreMaxMidiPort)
@@ -974,7 +1010,7 @@ void Seq::initInstruments(bool realTime)
                         sendEvent(event);
                   }
             // Setting pitch bend sensitivity to 12 semitones for external synthesizers
-            if ((preferences.getBool(PREF_IO_JACK_USEJACKMIDI) || preferences.getBool(PREF_IO_ALSA_USEALSAAUDIO)) && mm.channel() != 9) {
+            if ((cachedPrefs.useJackMidi || cachedPrefs.useAlsaAudio) && mm.channel() != 9) {
                   if (realTime) {
                         putEvent(NPlayEvent(ME_CONTROLLER, channel->channel(), CTRL_LRPN, 0));
                         putEvent(NPlayEvent(ME_CONTROLLER, channel->channel(), CTRL_HRPN, 0));
@@ -1206,7 +1242,7 @@ void Seq::seek(int utick)
 
 void Seq::seekRT(int utick)
       {
-      if (preferences.getBool(PREF_IO_JACK_USEJACKTRANSPORT) && utick > endUTick)
+      if (cachedPrefs.useJackTransport && utick > endUTick)
                   utick = 0;
       seekCommon(utick);
       setPos(utick);
@@ -1295,7 +1331,7 @@ void Seq::stopNotes(int channel, bool re
             if (cs->midiChannel(channel) != 9)
                   send(NPlayEvent(ME_PITCHBEND,  channel, 0, 64));
             }
-      if (preferences.getBool(PREF_IO_ALSA_USEALSAAUDIO) || preferences.getBool(PREF_IO_JACK_USEJACKAUDIO) || preferences.getBool(PREF_IO_PULSEAUDIO_USEPULSEAUDIO) || preferences.getBool(PREF_IO_PORTAUDIO_USEPORTAUDIO))
+      if (cachedPrefs.useAlsaAudio || cachedPrefs.useJackAudio || cachedPrefs.usePulseAudio || cachedPrefs.usePortAudio)
             _synti->allNotesOff(channel);
       }
 
@@ -1511,7 +1547,7 @@ void Seq::putEvent(const NPlayEvent& eve
       _synti->play(event, syntiIdx);
 
       // midi
-      if (_driver != 0 && (preferences.getBool(PREF_IO_JACK_USEJACKMIDI) || preferences.getBool(PREF_IO_ALSA_USEALSAAUDIO) || preferences.getBool(PREF_IO_PORTAUDIO_USEPORTAUDIO)))
+      if (_driver != 0 && (cachedPrefs.useJackMidi || cachedPrefs.useAlsaAudio || cachedPrefs.usePortAudio))
             _driver->putEvent(event, framePos);
       }
 
@@ -1734,7 +1770,7 @@ void Seq::handleTimeSigTempoChanged()
 void Seq::setInitialMillisecondTimestampWithLatency()
       {
      #ifdef USE_PORTMIDI
-           initialMillisecondTimestampWithLatency = Pt_Time() + preferences.getInt(PREF_IO_PORTMIDI_OUTPUTLATENCYMILLISECONDS);
+           initialMillisecondTimestampWithLatency = Pt_Time() + cachedPrefs.portMidiOutputLatencyMilliseconds;
            //qDebug("PortMidi initialMillisecondTimestampWithLatency: %d = %d + %d", initialMillisecondTimestampWithLatency, unsigned(Pt_Time()), preferences.getInt(PREF_IO_PORTMIDI_OUTPUTLATENCYMILLISECONDS));
      #endif
      }
--- a/mscore/seq.h
+++ b/mscore/seq.h
@@ -165,6 +165,28 @@ class Seq : public QObject, public Seque
       QTimer* heartBeatTimer;
       QTimer* noteTimer;
 
+      /**
+       * Preferences cached for faster access in realtime context.
+       * Using QSettings-based Ms::Preferences directly results in
+       * audible glitches on some systems (esp. MacOS, see #280493).
+       */
+      struct CachedPreferences {
+            int portMidiOutputLatencyMilliseconds = 0;
+            bool jackTimeBaseMaster = false;
+            bool useJackTransport = false;
+            bool useJackMidi = false;
+            bool useJackAudio = false;
+            bool useAlsaAudio = false;
+            bool usePortAudio = false;
+            bool usePulseAudio = false;
+
+            void update();
+            };
+      CachedPreferences cachedPrefs;
+
+      void startTransport();
+      void stopTransport();
+
       void renderChunk(const MidiRenderer::Chunk&, EventMap*);
       void updateEventsEnd();
 
@@ -266,6 +288,8 @@ class Seq : public QObject, public Seque
 
       void setInitialMillisecondTimestampWithLatency();
       unsigned getCurrentMillisecondTimestampWithLatency(unsigned framePos) const;
+
+      void preferencesChanged() { cachedPrefs.update(); }
       };
 
 extern Seq* seq;
