Features Help Download

Lomse Hacking Guide

2.3. Score model

2.3.1. Design rationale for the score model

These are the objects that define the score model:

ImoObj
  |
  +-- ImoSimpleObj                  Just contains other objects or
  |     |                           properties
  |     |
  |     +-- ImoContainerObj         A container for ImoContentObj
  |           |
  |           +-- ImoInstrument     A container for ImoStaffObj
  |
  +---ImoContentObj
        |
        +-- ImoBoxLevelObj          A box-level object
        |     |
        |     +-- ImoScore          A container for instruments
        |
        +-- ImoScoreObj             Content for scores
              |
              +-- ImoStaffObj       Main music content objects
              |
              +-- ImoAuxObj         Modifiers. Can go attached to any score object
              |
              +-- ImoRelObj         Relations. Relates two or more StaffObjs.

The approach followed has been to capture the score structure by splitting the representation in containers and content objects. For example, we could imagine the staff as a container for notes and rests.

There are only two container classes: the score (ImoScore object) and the instrument (ImoInstrument class). The score is a container for instruments, and an instrument is a container for the objects representing the music for that instrument. But although bot, score and instrument, are container classes, there is an important difference between them: the score is a box level object, that is, it is a constituent block for document content. But not the instrument, which is just an auxiliary collection of objects. Because of this reason, score and instrument are in different places in the hierarchy. ImoScore derives from ImoBoxLevelObj and ImoInstrument from ImoContainerObj.

All other score related objects are considered content objects, and are modeled by class ImoScoreObj. All these objects that can be included in a score are classified in two main groups: staff objects (ImoStaffObj class) and auxiliary objects (classes ImoAuxObj and ImoRelObj).

An ImoInstrument is, basically, a collection of ImoStaffObj objects. You can think about staff objects as those symbols that are placed on the staff and are the basis for describing the music: I am are referring to symbols such as notes, rests, clefs, time and key signatures, barlines or other symbols with similar characteristics: objects that must me placed on the staff, are ordered by time and are essential to describe a melodic line (see: ImoStaffObj vs. ImoAuxObj: implementation criteria). But, in practice, instead of adding the ImoStaffObj nodes as direct children of ImoInstrument, an auxiliary node ImoMusicData is created. This does not cause any performance or memory burden, facilitates the access to contained staff objects, and mimics the LDP structure, where all staff objects are defined inside a ‘musicData’ tag:

            ImoScore
                |
           ImoInstrument
                |
           ImoMusicData
                |
   +------------+------------+
   |            |            |
ImoClef      ImoNote      ImoNote

2.3.2. Auxiliary objects: classes ImoAuxObj and ImoRelObj

All other objects in the score that are not ImoStaffObj objects are considered auxiliary objects, They are like modifiers describing additional properties or adding some editorial highlight on something. For instance, a fermata modifies the duration of a note. A tie modifies the two tied notes by joining their durations, And a title, adds editorial information to an score.

There are two types of auxiliary objects. The first group is formed by those auxiliary objects representing additional properties or modifiers for the owner object (one-to-one relations). For instance, a fermata modifies the duration of the note owning the fermata. These auxiliary objects will be named simple auxiliary objects and will be modeled by objects derived from class ImoAuxObj. They are included in the internal model just as children nodes in the objects they modify. For instance, a fermata in a note is modeled by a ImoFermata child node, whose parent is the ImoNote object. Or the title for an score is an ImoAuxObj attached to the ImoScore. The following example illustrates this:

Modeling a fermata (simple auxiliary object)

Figure: Modeling a fermata (simple auxiliary object).

In LDP this score is written as:

(score (vers 1.6)
    (instrument
        (musicData
            (clef G)
            (n g4 q)
            (n c5 q (fermata above))
            (barline)
        )
    )
)

And it will create the following internal tree:

                   ImoScore
                       |
                  ImoInstrument
                       |
                  ImoMusicData
                       |
   +------------+------+-----+------------+
   |            |            |            |
ImoClef      ImoNote      ImoNote    ImoBarline
                             |
                       ImoAttachments
                             |
                         ImoFermata

Notice that, in practice, ImoFermata is not a direct child of ImoNote. Instead, an intermediate container node ImoAttachments is created. This is just to keep together all ImoAuxObj objects attached to a parent node.

ImoAuxObjs can be attached to any score object. For instance, a title to an score, And also to other ImoAuxObj objects. For instance a text notice attached to a fermata that, in turn , is attached to a note.

The other group of auxiliary objects is formed by those objects modeling a relationship between two or more staff objects. For instance, a tie, and slur, or a dynamics wedge. These auxiliary objects are modeled by abstract class ImoRelObj.

ImoRelObj objects can not be included in the internal tree as children of the staff objects that are part of the relationship, as this would transform the tree into a network. For instance, consider the following score with two tied notes:

A tie (relationship auxiliary object) can not be modeled as a child node.

Figure: A tie (relationship auxiliary object) can not be modeled as a child node.

In LDP this score is written as:

(score (vers 1.6)
    (instrument
        (musicData
            (clef G)
            (n a4 q (tie 1 start))
            (n a4 q (tie 1 stop))
            (barline)
        )
    )
)

If the tie is modeled as a child node common to the related notes, it would create the following invalid tree:

             ImoScore
                 |
            ImoInstrument
                 |
            ImoMusicData
                 |
   +--------+----+---+---------+
   |        |        |         |
ImoClef  ImoNote  ImoNote  ImoBarline
            \        /
             \      /
              ImoTie

For this reason, ImoRelObj objects can not be nodes in the tree model. To solve this problem, they are stored not as child nodes but as internal data in a list of attached ImoRelObj objects. This list is contained in node ImoRelations:

                     ImoScore
                         |
                    ImoInstrument
                         |
                    ImoMusicData
                         |
     +-------------+-----+-------+---------+
     |             |             |         |
  ImoClef       ImoNote       ImoNote  ImoBarline
                   |             |
              ImoRelations  ImoRelations
                   \            /
--- --- --- --- --- --- --- --- --- ---- ---
                     \       /    Not part of the tree. Stored
                       ImoTie     as object data

So, simple auxiliary objects (ImoAuxObj) are inserted as children in a ImoAttachments child node, and relation auxiliary objects (ImoRelObj) objects are stored as internal data in a ImoRelations child node:

                             |
                 +-----------+-----------+
                 |                       |
              ImoNote                 ImoNote
                 |                       |
       +---------+---------+       ImoRelations
       |                   |             |
ImoAttachments       ImoRelations        |
       |                   \            /
  ImoFermata          - --- --- --- --- --- ---- ---
                             \       /    Not part of the tree
                               ImoTie

2.3.3. Relation data objects: class ImoRelDataObj

In any relationship you will find two types of data:

  1. Data about the relationship itself: its existence, its properties and information that is common to all participants. For instance, class ImoBeam represents the grouping of several notes/rests by using a beam, and it contains the information that is common to all notes included in the beam, such as the beam color.
  2. Specific data for each participant in the relationship, such as its role. For instance, for a beam, it is necessary to specify, for each note in the beam, the details about how to draw the beam for that note: as continuous line, as a forward hook, as a backward hook, etc.

Objects derived from class ImoRelObj represents a relationship, but they only contains the first type of data: the relationship properties and information that is common to all participants.

For modeling the specific data about each participant, another base class is used: class ImoRelDataObj. For instance, to model a group of tree beamed notes, the following objects are created:

  • One ImoBeam object, derived from ImoRelObj. It represents the beam.
  • Three ImoBeamData'' objects, derived from ``ImoRelDataObj. They represent the specific beaming information for each note: i.e. the beam type.

Each data object is associated to each participant object (i.e. the notes in the beam) by creating a std::pair object. And all these pairs are stored in the ImoRelObj in a list of participants. Thus, the internal structure for any ImoRelObj is the following:

ptrs. to participants:  ImoStaffObj          ImoStaffObj
                           ^                    ^
ptrs. to participants'     |                    |
data (i.e. its role):      | ImoRelDataObj      | ImoRelDataObj
                           |     ^              |     ^
                           |     |              |     |
                   +- -- --|-- --|-- -- -- -- --|-- --|-- -- -- ...
List of            |  first|     |second   first|     |second
participants -----------> std::pair ---------> std::pair -------> ...
                   |
                   |
                   +- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ...
                   ImoRelObj

In some relations, there is no specific data to store in the ImoRelDataObj objects. In these cases no ImoRelDataObj` is created, and the internal pointers in ImoRelObj contains NULL.

Here are the objects involved in modeling currently implemented relationships:

ImoRelObj                  ImoRelDataObj
  |                           |
  +-- ImoBeam                 +-- ImoBeamData
  +-- ImoChord                |      no chord data
  +-- ImoTie                  +-- ImoTieData
  +-- ImoTuplet               +-- ImoTupletData
  +-- ImoSlur                 +-- ImoSlurData

2.3.4. Notes about some objects

2.3.4.1. ImoSpacer and anchor objects

ImoSpacer is an ImoStaffObj whose purpose is to add more space between the staff objects than precede and follow the ImoSpacer. That is, it is like the space character in text processing.

But ImoSpacer plays also an important role: it is the anchor object for attaching auxiliary objects to an staff. As ImoAuxObj objects are not ImoStaffObj they can not be included directly as content for an staff. Remember that ImoAuxObj objects can only be attached to staff objects. To avoid creating artificial dependencies (attaching auxiliary objects to notes or rest that have nothing to do with the attached object) it is better to attach the auxiliary object to a ‘neutral’ staff object with no meaning, an anchor object: an ImoSpacer of zero width.

2.3.5. ImoStaffObj vs. ImoAuxObj: implementation criteria

When having to implement a new notation object, sometimes it is difficult to decide if it should be an ImoStaffObj or an ImoAuxObj. The best approach is to consider how the music line (melody) is affected by the presence of the new notational object:

  1. Notations that are better associated to the staff than to a particular staff object. The notation affects to all the music defined after it. In this cases, normally the best option is to model that notational object as an ImoStaffObj. For instance, at first sight a metronome mark looks like a text at beginning of the score. So you could thing of it as an attached text and, therefore, think about modeling metronome marks as an ImoAuxObj. But, a metronome mark has musical meaning, and affects the tempo of all notes/rest coming after the metronome mark. So, it is a kind of musical event that affects all coming musical events. Because of this, it is a kind of staff object not a property of a particular staff object.
  2. Notations with no meaning when associated to an staff. They only have musical meaning when associated to an staff object and affects only to that staff object. In these cases, normally the best option is to model that notational object as an ImoAuxObj (or ImoRelObj is several objects are involved). For instance, lyrics, or all articulations and technical markup associated to notes/rests.
  3. Elements with no musical meaning but affecting all staff objects. For example, objects for layout control, such as page breaks or system breaks. They are better modeled as ImoStaffObj objects.

In summary, the key rule is use ImoStaffObj for measure-attached objects that should affect all objects coming after it. On the contrary, use ImoAuxObj/ImoRelObj for note-attached objects that should affect only to the parent objects.