Iterating through measures (bars) in a staff for all staffs
Hello,
As said in the subject, I would like to iterate through all measures (bars) in a staff for all staffs. So I asked Microsoft Copilot and it suggested something that looked sensible:
console.log("Iterating through all measures in all staves...");
// Iterate through all staves in the score
for (let staffIndex = 0; staffIndex < curScore.staves.length; staffIndex++) {
let staff = curScore.staves[staffIndex];
console.log("Staff " + (staffIndex + 1) + ":");
// Iterate through all measures in the staff
for (let measureIndex = 0; measureIndex < staff.measures.length; measureIndex++) {
let measure = staff.measures[measureIndex];
console.log(" Measure " + (measureIndex + 1) + ":");
// Iterate through all elements in the measure
for (let elementIndex = 0; elementIndex < measure.elements.length; elementIndex++) {
let element = measure.elements[elementIndex];
// Check if the element is a note
if (element.type === Element.Note) {
console.log(" Note: pitch=" + element.pitch + ", duration=" + element.duration);
}
}
}
}
Unfortunately, it does not work. In particular, in Musescore 4.4.4 on Ubuntu 20.04, staff.measures.length
does not seem to exist, perhaps not even staff.measures
as the error indicates:
Cannot read property 'length' of undefined
What would be a neat way to iterate through measures?
Thanks in advance and kind regards,
peter
Comments
Measures
In reply to var seg; var tick; var mez;… by yonah_ag
Thanks for the prompt reply. A few questions:
What is a
Segment
, and what is aTick
?And how would I get hold of the notes within that measure?
If I want to build some meta data per measure, and create a map for that purpose, how would I be able to able to identify each measure.
In reply to Thanks for the prompt reply… by Peter Wurmsdobler
The score is sliced into time slots called segments.
Whenever an element exists at a point in time there will a segment. I think of it as a vertical line drawn through all the staves with a horizontal time position of tick.
There are 480 ticks in a ¼ note.
Every stave has 4 tracks, (corresponding to the 4 voices that it can contain). Tracks are number from zero on the first stave and continue sequentially down all staves. So a piano score of treble and bass clef would have tracks 0 to 7.
Multiple tracks can have elements at the same tick, in different staves.
In reply to The score is sliced into… by yonah_ag
Thanks a lot, that helps me building an "object model" in my mind which I would have expected a good API documentation to include, see https://musescore.org/en/node/379529
In reply to The score is sliced into… by yonah_ag
Interesting, "There are 480 ticks in a ¼ note.", What is the reasoning behind that?
480 = 10 * 12 * 4, not even a power of 2. One would have thought that time is discretised in the possibly smallest entity of a note, perhaps 1/2^(N), with perhaps a bit of an allowance.
In reply to Interesting, "There are 480… by Peter Wurmsdobler
I believe that stems from MIDI
Also you can divide it by 5, 3 and 2, so can have triplets and 5lets and also dupltes, quadruplets 6lets, 8lets and dotted durations and still allows for 128th notes
The next better value would be 3360, as it'd also allow for 7lets, without rounding
In reply to I believe that stems from… by Jojo-Schmitz
That was my thinking too. It's highly divisible. I think that the default for MusicXML is 240, (like the pre decimal pence in a UK Pound Sterling), but MS goes the extra step.
In reply to I believe that stems from… by Jojo-Schmitz
Thanks, that makes sense. The discretisation in a unit that allows a 1/4 (or an 1/8, 1/16, 1/32) to be split further into n-lets as well as dotted durations.
In reply to var seg; var tick; var mez;… by yonah_ag
Notes are an array on chord elements.
This code adds text annotations to notes in staves.
(The variables used in notes[ii].visible block are set higher up and not relevant to you query).
This code steps through segment-by-segment rather than using measure processing as I'm not interested in measures for this particular plugin.
In reply to Notes are an array on chord… by yonah_ag
You can find the cursor rewind modes here:
https://musescore.github.io/MuseScore_PluginAPI_Docs/plugins/html/class…
0 = start of score
1 = start of selection
In reply to Notes are an array on chord… by yonah_ag
Thanks again, I will have to ingest that as it looks a bit convoluted. I feel thrown back to coding styles of the 1990s, C-like, early C++, abbreviations for variable names, globals, lists, iterating through lists with indices, indexing, magic numbers, all very error prone. Perhaps I have been spoiled in the recent years learning more modern paradigms in python, list comprehensions, iterators, generators, map/filter/reduce, etc.
In reply to Thanks again, I will have to… by Peter Wurmsdobler
You don't need to use any magic numbers as there are enumerations available.
In reply to You don't need to use any… by yonah_ag
Thanks, yes, the API does mention some enumerator type, but it is not always clear if they are FULLCAPS, or CamelType, and if a fully qualified path is needed.
In reply to var seg; var tick; var mez;… by yonah_ag
The MS3 part of this plugin is a Score Mapper. You could tailor it to suit your needs.
https://musescore.org/en/project/import-velocity
I feel like pulling my hair out; what in my opinion should be such a standard use case turns out to be so tedious: iterating over notes in measures in staves. The following code seems to see measures but not notes:
What am I missing?
Once I get through that I promise I will write up a page for newbies like me with simple concepts and boiler plate code to do some basic operations.
In reply to I feel like pulling my hair… by Peter Wurmsdobler
Unless I need measure info I just iterate through the score segment by segment, (ignoring measure boundaries), then by chordrests then by chords, which have an array of notes as a property.
You can process tracks as an inner loop or outer loop if multiple tracks are involved.
In reply to Unless I need measure info I… by yonah_ag
My impression is that objects are being referenced at various stages. Say a score allows iterating over all segments means that the score object maintains a list of segments (or a linked list); each segment may contain a list of elements, perhaps as an alias, but also contains a list of tracks, then chords, and what have you. The object model is very, very confusing, even after reading https://musescore.org/en/developers-handbook/references/musescore-inter… several times. A diagram would help me greatly, or some example score with a visualisation of an object hierarchy, differentiating between child and parent references and aggregation.
In reply to My impression is that… by Peter Wurmsdobler
I'll make a diagram but for now:
• The segments are not linked lists. They are time slices along the score's horizontal axis.
• There are different types of segments.
For note processing you are looking for ChordRest segments.
In reply to I feel like pulling my hair… by Peter Wurmsdobler
Try this plugin to reveal the object model of a score. Use a small selection to start with as it's very slow – but comprehensive.
https://musescore.org/en/project/element-analyser
In reply to Try this plugin to reveal… by yonah_ag
Yes, I have looked at this code many times, still bewildered by the hacker coding style resulting in many WTF moments.
In reply to Yes, I have looked at this… by Peter Wurmsdobler
The output is very helpful.
In reply to I feel like pulling my hair… by Peter Wurmsdobler
So I installed Qt6 & QtCreator, git cloned MuseScore at commit 0342e5cc8d2afcbceb938897543b588a7fc5d4ab on master, and spent some time studying the source code. There I can see as explained more or less in https://musescore.org/en/developers-handbook/references/musescore-inter…
As defined in
score.h
the Score containsMeasure
s as:where
MeasureBaseList
contains a tick-keyed multi-map from tick to a collection of measure-base(s), either a singleMeasure
(a bar?) and/or multiple boxes.Being only interested in notes, as defined in
measure.h
, a realMeasure
containsSegment
s as:which is a container for
Segment
objects which in turn areEngravingItem
s and can be of variousSegmentType
s. TheChordRest
is of interest to me.Finally, the
Chord
containsstd::vector m_notes
, so all fine.Suppose, I have a single 4/4 staff with 2 measures, and in each say 4 1/4 C4 notes, I would expect the following:
-
m_measures
contains twoMeasure
objects, one at tick 0, and one at tick 480*4.- the first
Measure
object contains a segment list with 4 Segments- each such segment would have the appropriate tick offset from the beginning of the measure
- each such segment would contain one chord containing each a C4 note.
Consequently, it should be possible to iterate over the measures in the score, in each measure over the segments, in each segment of type chord over the notes. Does this make sense?
In reply to So I installed Qt6 &… by Peter Wurmsdobler
It does. That would work. I just don't bother with measures unless they are needed in the plugin. Instead I iterate only over segments.
This is for MS3 but might help to clarify things:
https://musescore.org/en/project/tab-walk
If you use Excel then the plugin's csv output can be viewed using the Excel workbook provided in the GitHub repo.
In reply to It does. That would work. I… by yonah_ag
So, to iterate through all notes in the score, you might use something like this pseudocode?
Say, I've never been able to include indented text in the forums without replacing it with dots as above. Is there some way to do that???
[Later] Either four spaces or tabs just give me non-indented text (as above). The spaces are simply dropped *pout*
[Still later] HOORAY!!! I didn't have four spaces before the first and last lines.
In reply to So, to iterate through all… by TheHutch
To show as a code block you use 4 leading spaces per indent level.
In reply to So, to iterate through all… by TheHutch
The TAB Walk example plugin, (linked above), fleshes out your pseudo code and also processes segment annotations as well chords and rests. It also deals with the different segment types separately.
It should work with non TAB scores but there won't be any string or fret data.
In reply to The TAB Walk example plugin,… by yonah_ag
Thanks for the TAB walk; I could adapt that to my needs quite well.
In reply to So, to iterate through all… by TheHutch
For what it's worth, I usually follow markdown syntax: https://www.markdownguide.org/extended-syntax/#fenced-code-blocks
In reply to It does. That would work. I… by yonah_ag
Here's a way of processing measure by measure then segment by segment. The 'magic number' 512 should be changed to the CHORDREST enum but I can't recall what it is. This code process the first 4 tracks of a score, i.e. the first stave.
In reply to It does. That would work. I… by yonah_ag
Dear @yonah_ag, that was a very good example indeed; together with looking at the source code the structure became much clearer.
The next bit is only to work out the calls, objects and properties available through the API; there the documentation is a bit lacking, but the C++ source does help.
Many thanks to all for very helpful advice. I have written up my current understanding, together with some diagrams and a simple plugin to walk through measures and segments so far; to be enhanced soon. See https://github.com/PeterWurmsdobler/musescore/tree/main/basicmuse for more details.
One aspect of MuseScore QML programming (in VSCode) I still find tedious is the lack of documentation for API calls, types and so on. I try to work with Qt creator at the same time, looking in the header files for what might be exposed to the API.
In reply to Many thanks to all for very… by Peter Wurmsdobler
That's a nice document.
There are a couple of other iteration methods that I use.
1) Sometimes I process staves without any reference to a measure loop by simply using the segment.next property in a segment loop. Then I'll typically process tracks as an inner loop.
From previous discussion with other users I understand that these loops could be swapped to tracks (outer) and segments (inner). It just depends on what feels more natural for the context.
2) Sometimes I don't process by stave at all but by track, with track number starting at 0 on stave 0 and then 4 tracks per stave down through all staves. A diagram showing tracks would also be useful.
Example, a 2 stave piano + 1 stave guitar tab could be processed as tracks 0-11.
In reply to That's a nice document… by yonah_ag
Thanks, I hope the simple demo will be helpful for others, too.
As for traversing elements within segments, my impression is that there is no simple mechanism such as:
as an inner loop within a segment, at least what I can tell from the public methods in
segment.h
. It would make sense to me as the track index (i.e. staff and voice indices) are "properties" of the elements and elements are the children of the segment (with segments being children of a measure, and a measure being the value of a score map.)More generally speaking, I do see that MuseScore uses the quite low level concepts of linked lists etc. In more modern paradigms, that would be nicer (language permitting) to express something like:
Update: I also posted a feature request on https://musescore.org/en/node/379664
In reply to Thanks, I hope the simple… by Peter Wurmsdobler
Because a segment slices through all tracks, (12 of them in the example just above), you need a track reference to get hold of an element at any segment tick.
In reply to Because a segment slices… by yonah_ag
Yes but, no but, or more seriously, I do recognise that a
Segment
"slices" through all tracks asSegment
docs say:The second line is a strong indication that
Segment
ought to be a C++ template class; for some reason however this class stores theSegmentType
; the elements of the same type (linked to theSegmentType
) are stored in its memberm_elist
as:So the segment could simply have a getter available through the plugin API (
const
and nonconst
):in addition to the
elementAt(track_idx_t track)
which simply returns the element at the index in them_elist
.One should still be able to get the track index from the
EngravingItem
's property if it was exposed to the plugin API:This means that the track index is both explicitly available for every
EngravingItem
, but also implicitly as an index into a private array in theSegment
; this redundancy has to be maintained and is error-prone.In summary, given the current code base, it is conceivable to get a list of all elements in the segment and then process those based on some filters to the author's liking.
In reply to Yes but, no but, or more… by Peter Wurmsdobler
I suppose that since MuseScore has been in development for many years some things persist for historical reasons that might be done differently if the software was re-developed from scratch.
I wonder if redundancy situations, (like the one above), could be involved in the apparently random score corruptions that continue to be a regular topic on the forum.
In reply to I suppose that since… by yonah_ag
A quick google search reveals: "Templates were introduced in Release 3.0 of the language, in October 1991" (https://belaycpp.com/2021/10/01/history-of-c-templates-from-c-style-mac…), so quite some time ago.
It is a difficult problem to have the same information available at different levels of an object model: either copies of that information with some mechanism to keep it consistent, or only in one place and derive as needed, with some performance implications.
After this long discussion I now do have a way to iterate through the score and print out certain properties. However, it appears iterating through all, including notes, does not mean that I can actually change properties of these objects, e.g. the note colour. Can only properties of selected elements be changed?
If that is the case, and if I collect the selected notes into a list, is there a way to get the staff index for a given note? Given that a
Note
derives from anEngravingItem
and the latter has a public propertystaffIdx
this should be possible (if exposed through the API).In reply to After this long discussion I… by Peter Wurmsdobler
If you have a note object in a variable then you can change many of its properties, including colour. I don't think that it matters whether you arrive at your note object by iterating through the score or by building an array of note objects to process. I have plugins which change many note properties without first making a selection.
Note is a sub-type of Element.
Element has a Track property from which you could calculate Staff Index.
In reply to If you have a note object in… by yonah_ag
Thanks, yes I found it out the hard way, i.e. by trying. There is indeed a
track
property, and avoice
property, too, but nostaff
property.In reply to Thanks, yes I found it out… by Peter Wurmsdobler
Track is sufficient. I wonder why voice is included.
In reply to Track is sufficient. I… by yonah_ag
There are two possibilities:
- provide
voice
andstaff
and hide the fact that there are 4 voices per staff;- provide
track
and make the number of voices per staff a property.Mixing two paradigms does not sound consistent.
In reply to There are two possibilities:… by Peter Wurmsdobler
• The number of voices per stave is always 4.
• The number of tracks per stave is always 4.
• Voices number from 0 to 3 for each stave.
• Tracks number from 0 to N from the first stave,
where N = 4S - 1, and S = number of staves.
Hence
• staffIdx = (track / 4)
• voice = (track % 4)
Therefore track alone is sufficient for all 3 properties.
In reply to • The number of voices per… by yonah_ag
Thanks, having studied the source code I could verify that the number of voices per staff is always
4
. However, it is not good practice to expose a number which is baked into the internals, e.g. as a vector of pointers at a fixed size of4
, pre-populated with null-pointers. Rather, the API should be one of two:- expose the number of voices per staff as symbol, e.g.
Score.VoicesPerStaff
which could be saved with a score if it every changed and made available to QML transparently;- do not expose the number of voices per staff at all, do not use a fixed size vector of pointers internally, but allow the voices to grow, then make
voice
andtrack
available as properties.In reply to Thanks, having studied the… by Peter Wurmsdobler
That would certainly make sense but I guess that MuseScore is keeping to the common standard in music software of always having 4 voices per stave. I mainly only use 1 or 2 voices but it could be useful to have 6 voices, e.g. for a classical guitar where each of the 6 strings can ring independently.
MuseScore doesn't seem to save any null values for empty voices, (you can check by saving in mscx format rather than in mscz), but maybe it wastes runtime memory for them in the object model.
In reply to That would certainly make… by yonah_ag
Musescore would probably not serialise a vector of nulls to file; a segment is however initialised with an allowance for 4 voices per staff in
Segment.cpp
lines 260ff:where
m_elist
is thevector
of pointers toEngravingItem
s.In reply to Musescore would probably not… by Peter Wurmsdobler
Have you considered joining the development of MS4?
In reply to Have you considered joining… by yonah_ag
The thought has crossed my mind; first I'll need to set up my development environment. Perhaps then I could pick up some tickets over time.