Add Metronome Part fails with changing time signatures. Found a fix, but why does it work?

• Dec 2, 2022 - 15:42

I found that with a score containing changes in beats-per-measure, Add Metronome Part does not work correctly.

I fixed it and am happy with the result, but I have no idea why this solution works. Probably my too naive understanding of plugin development. Can anybody enlighten me? Thanks!

For reference, the code as downloaded through https://musescore.org/en/project/add-metronome-part is

import QtQuick 2.0
import MuseScore 3.0
 
MuseScore {
   menuPath: "Plugins.Add Metronome Part"
   description: "Add metronome part"
   version: "0.1"
   requiresScore: true
 
   onRun: {
      console.log("...")
 
      curScore.appendPart("wood-blocks")
      var idx = curScore.nstaves-1
 
      curScore.parts[idx].isMetro = true
 
      var c = curScore.newCursor()
      c.rewind(0)
      c.staffIdx = idx
      c.voice = 0
      if( c.measure.timesigActual.str != c.measure.timesigNominal.str )
         c.nextMeasure()
      do{
         c.setDuration( 1, c.measure.timesigActual.denominator )
         for( var i=1; i <= c.measure.timesigActual.numerator; i++ )
            c.addNote( i==1 ? 76 : 77, false )
      }while( c.prev() && c.nextMeasure() )
 
      Qt.quit()
   }
}

I did a few experiments, all from the Plugin Maker. The results are shown in the image below, together with a test score.
First experiment was running the plugin as is.
The result is seen in the first Woodblock staff: no ticks.
The Plugin Maker gives a TypeError on the line
        curScore.parts[idx].isMetro = true
.Apparently, this line is not allowed in my version 3.6.2. I commented it out with no apparent ill side effects.
Commenting out the line gives some result, but not correct yet. See the second Woodblocks staff, measure 5. It looks like measure 4 spills over in 5, and the rest of 5 is skipped. The code looked correct to me, however.
Some experimenting led me to introduce a new variable at the beginning of the Do loop:

        var numerator = c.measure.timesigActual.numerator

and use its value instead of the full expression as controlling variable in the For loop. The result is in the third Woodblock staff, and is as desired. The final code is:

import QtQuick 2.0
import MuseScore 3.0
 
MuseScore {
   menuPath: "Plugins.Add Metronome Part"
   description: "Add metronome part"
   version: "0.1"
   requiresScore: true
 
   onRun: {
      console.log("...")
 
      curScore.appendPart("wood-blocks")
      var idx = curScore.nstaves-1
 
      //curScore.parts[idx].isMetro = true
 
      var c = curScore.newCursor()
      c.rewind(0)
      c.staffIdx = idx
      c.voice = 0
      if( c.measure.timesigActual.str != c.measure.timesigNominal.str )
         c.nextMeasure()
      do{
         var numerator = c.measure.timesigActual.numerator
         c.setDuration( 1, c.measure.timesigActual.denominator )
         //for( var i=1; i <= c.measure.timesigActual.numerator; i++ )
         for( var i=1; i <= numerator; i++ )
            c.addNote( i==1 ? 76 : 77, false )
      }while( c.prev() && c.nextMeasure() )
 
      Qt.quit()
   }
}

Why does this substitution work? It seems to me that it should not make any difference. Thanks for any help.

example.png

Attachment Size
example.png 118.76 KB

Comments

Hi jeroen2442,
Your question comes down to

why this does not work

         for( var i=1; i <= c.measure.timesigActual.numerator; i++ )
            c.addNote( i==1 ? 76 : 77, false )

while this works

         var numerator = c.measure.timesigActual.numerator
         for( var i=1; i <= numerator; i++ )
            c.addNote( i==1 ? 76 : 77, false )

I think it's because the cursor reference is mutated by .addNote() method (see jeetee's comment)
for loop at measure marked 4 runs 4 times instead of expected 3 times, the 4th time the conditional being (cursorAtMeasure5).measure.timesigActual.numerator

Sidetrack question: curScore.appendPart is undocumented but seems to work, contrary to this, should correct wiki?

In reply to by msfp

Thanks for pointing this out to me! I had implicitly assumed that the termination condition for a loop is determined and set at a fixed value at the time the loop is initiated.

I now know that is not how it works in JavaScript. The call is by reference, not by value. In this case the condition changes during the loop, which has unintended side effects. My fix solved this, without me realizing what it is that it being fixed.

Learned a new thing, thanks!

Do you still have an unanswered question? Please log in first to post your question.