Features Help Download

Lomse Hacking Guide

Caution

This page just draft material, not yet finished. Also I have not yet clear ideas about how to organize this chapter. Nebertheless, all material presented here is reviewed and valid, but subject to reorganizations and changes.

3.6. Engravers

3.6.1. Engraver objects

ñEngraverñ objects are responsible for creating the shape objects. Normally, method ñcreate_shape()ñ has the responsibility for this. But in some cases (i.e. shapes for relations, such as a tuple, a beam, a slur, etc.) the shape can not be finished until the shapes for all the related objects have been created. In these cases, final step in shape creation takes place by invoking method ñfinish_shape()ñ

An engraver MUST only create one shape (although if it is a composite one, the engraver must, internally, create the component shapes). If many shapes of the same type must be created (i.e. secondary key signature shapes for all staves in an instrument) it is responsibility of the invoker layouter to determine how many shapes are requierd and to invoke the engraver as many times are the number of needed shapes.

The engraver receives a reference position for shape placement.

Engravers’ responsibilities:

  • Create the shapes
  • Provide information to other layouters/engravers about created shapes
  • Some shapes are created by different engravers (i.e. shape dot, shape accidental). Must shape X be created by engraver X or, any engraver can create any shape?

    Shapes must be created by engravers. Therefore, to enforce this policy, all shapes constructors must be protected, and the engravers allowed to create those shapes have to be defined as friend classes.

    TO_FIX: For controls, it has been decided that GmoObj objects can be created by the ñControlñ object. Therefore, these objects can create shapes. But as ñControlñ derived objects are created by user applications, it is necessary to decide which shapes will be allowed to be created by controls, and then decide which alternative:

    • add ñControlñ class as friend class for those shapes –> Doesn’t work.
    • Define a ñControlEngraverñ class? It will be a kind of factory class to create the shapes allowed to be created by ñControlñ objects.

    TO_FIX: Some shapes are used in unit tests. Its constructor is invoked directly and, therefore, can not be protected.

  • No uniform approach: Most engravers are oriented to engrave an ImoObj. But a few are oriented to engrave a shape.

    Both types of engravers (shape oriented, and ImoObj oriented) should be allowed. It is just an issue of the parameters required by the constructor. In general, used ImoObj oriented engravers for ImoObj objects, and shpae oriented engravers for helper shapes.

  • Named glyphs vs. anonymous glyphs

    For glyph shapes that are only used as component shapes (that is, shapes used only inside a composite shape), it is not important if they are treated just as generic glyph shapes or if they are implemented as derived classes with specific names. Named glyphs have a cost (define the class, define a new ID for the class, create a member function for RTTI). But named glyphs are useful for trace/debug methods (i.e. it is better to see something labeled as “clef shape” than to see “glyph shape”) and, sometimes, for processing (i.e. find the notehead shape or the clef shape). A balance bettween cost and legibility can be achieved by allowing anonymous glyphs for those glyphs that are of little interest in trace/debug. But, for now, there has been no need for many glyph derived classes:

    GmoShapeGlyph:

    +—— used as main shapes | - GmoShapeNotehead - GmoShapeFlag - GmoShapeDot - GmoShapeRestGlyph x GmoShapeClef x GmoShapeFermata - GmoShapeAccidental - GmoShapeTimeDigit - GmoShapeMetronomeGlyph

3.6.2. Notes about some engravers

3.6.2.1. BarlineEngraver

Ref.pos: barline pos

3.6.2.2. ClefEngraver

Ref.pos: clef pos.

3.6.2.3. InstrumentEngraver

Ref.pos: Info. provided:

  • staff position and size
Keeps:
  • the ñGmoShapeStaffñ objects for current system

3.6.2.4. KeyEngraver

Ref.pos: position of key in first staff of instrument. Engraver must determine positions for all other secondary key shapes. To this, uses parent InstrumentEngraver, to determine staves’s positions.

3.6.2.5. NoteEngraver

Ref.pos: note pos

3.6.2.6. TextEngraver

Ref.pos:

3.6.2.7. Paper cursor point

ñScoreLayouterñ maintains a ‘page cursor’ (ñm_pageCursorñ). Engravers for shapes receives this cursor as a first reference about the place in which the shape must be placed.

For engraving ñImoStaffObjsñ this reference normally points to the desired position on the staff. The y position always point to top line (5th line) of top staff in current system. And the x postion is the next available position after the previos ñGmoShapeñ.

Normally, the engraver will place the new shape in the received x position, and the y position will be adjusted to move, vertically, the shape to it right point.

3.6.2.7.1. Shapes derived from ñGmoShapeGlyphñ

Many shapes are based on symbols rendered using a music font. Shape positioning for glyphs is as follows:

3.6.3. Engravers - Create shape method

The ñEngraverñ bas eclass requires a pCreatorImo parameter, to be set as the ImoObj responsible for creating the shape.

The dilemma I always have is that pCreatorImo parameter can have a double meaning: #. the object for which a shape is to be created. i.e. ImoClef -> create clef shape #. the owner object for the created shape: i.e. ImoTuplet -> create text shape for tuplet number.

Therefore, there are two approaches for designing an engraver: #. Oriented to engrave an ImoObj. In this case, all necessary information is in pCreatorImo and, therefore, normally no more additional information is needed. #. Oriented to engrave a shape. In this case, pCreatorImo could be NULL and all necessary information must be passed as parameters.

First approach is more restrictive than second one. For instance, a shape for a clef can only be created by passing an ImoClef object to the engraver. This prevents using the engraver for creating clef shapes for other purposes (i.e. for inserting music symbols in a text, as if they were another character). But it is simpler as all information is on one parameter.

The Law of Demeter design rule suggests that the second approach should be preferred. This is because the engraver will not need knowledge about the innards of the objects it manipulates, pCreatorImo, in this case. As suggested in Claer Code book (p.xx) passing a parameter to extract the real parameters from it is not a good design principle.

Clean Code: A Handbook of Agile Software Craftsmanship Robert C. Martin, Prentice Hall, 2008

p.288, Chapter 17: Smells and Heuristics

F1: Too Many Arguments Functions should have a small number of arguments. No argument is best, followed by one, two, and three. More than three is very questionable and should be avoided with prej- udice. (See “Function Arguments” on page 40.)

p.43 Function Arguments

Argument Objects When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own. Consider, for example, the difference between the two following declarations: Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius); Reducing the number of arguments by creating objects out of them may seem like cheating, but it’s not. When groups of variables are passed together, the way x and y are in the example above, they are likely part of a concept that deserves a name of its own.

But (p.98) The Law of Demeter There is a well-known heuristic called the Law of Demeter that says a module should not know about the innards of the objects it manipulates. As we saw in the last section, objects hide their data and expose operations. This means that an object should not expose its internal structure through accessors because to do so is to expose, rather than to hide, its internal structure.

But in our case, ImoObj objects are basicaly data structures. They are purpose independent containers for the attributes that describe the object. And they do not have functions that do significant things. They basically have public accessors that, for all intents and purposes, make the private variables public.

So, it seem that the best approach is to pass just the ImoObjs instead of passing a lot of parameters.

3.6.4. Current engravers/engrouters

+—— ImoObj oriented | +– shape oriented | |
Staffobj engravers | |
AccidentalsEngraver - x BarlineEngraver x x ClefEngraver - x InvisibleEngraver - x (in lomse_score_layouter.cpp) KeyEngraver x - MetronomeMarkEngraver x - NoterestEngraver (A) NoteEngraver x - RestEngraver x - TimeEngraver - x
Simple aux object engravers
FermataEngraver x - LineEngraver x - TextEngraver x x
Relation auxobject engravers
BeamEngraver x ChordEngraver x SlurEngraver TieEngraver TupletEngraver
Other engravers
InstrumentEngraver x - StemFlagEngraver x - (in lomse_note_engraver.cpp)
engrouters (in lomse_box_content_layouter.cpp)
ButtonEngrouter x ImageEngrouter x WordEngrouter
Unused shapes:

GmoShapeRectangle

Engraver
LibraryScope& m_libraryScope; ScoreMeter* m_pMeter; int m_iInstr; int m_iStaff; LUnits tenths_to_logical(Tenths value); double determine_font_size();
RelAuxObjEngraver
GmoShape* m_pShape; virtual void set_start_staffobj() virtual void set_middle_staffobj() virtual void set_end_staffobj() virtual int create_shapes() = 0; virtual int get_num_shapes() = 0; virtual ShapeBoxInfo* get_shape_box_info(int i) = 0; virtual void set_prolog_width(LUnits width) {} virtual GmoShape* get_shape() { return m_pShape; }

OtherEngraver