Lomse library. API documentation  0.30.0
Editing documents overview

How to modify a document

When a source document (in LDP or MusicXML format) is loaded, it is not stored as text or as an XML Document Object Model (DOM). Instead, the document is parsed and an Internal Model is built. The Internal Model is, mainly, a tree structure in memory. The root element of the Internal Model tree represents the whole document. And the children of the root element represent the basic blocks for building a document: headers, paragraphs, music scores, lists, etc. For an overview of the Internal Model see The Document API.

Once the Internal Model is created, for modifying the document your application has two options, either

  1. use the edition commands API; or
  2. directly modify the internal model.

Edition commands are a high level API for supporting edition. It is based on executing commands and provides undo/redo (that is, reverting the last change done to the document and to reverting the undo by re-executing the undone command). It is appropriate for applications whising to provide an interface to the user for editing documents (e.g. a music score notation editor application).

Direct internal model manipulation is a low level API. Your application directly accesses the Internal Model and modifies the internal Internal Model structures. Obviously, there are no high level related services such as undo/redo and good knowledge of the internal model and related structures is needed for maintainin consistency. This API, faster and more flexible, is appropriate for applications that programatically create and modify documents (e.g. an application for creating random music scores).

Of course, both APIs are compatible and can be used by your application for offering different services.

The rest of this page describes the high level API using edition commands. For a description of the low level API see The Document API.

The high-level API: edition commands

All edition commands are objects derived from base abstract class DocCommand, and they work by modifying the Internal Model tree, either by inserting or removing elements or by modifying the attributes of an element. For instance, CmdInsertBlockLevelObj() is a command for inserting a block level object. This command, as well as most commands for inserting new elements, require a parameter with the source code of the element to insert. Currently the source code for all edition commands must be in LMD format.

Executing a command is just invoking Interactor::exec_command() method and passing the command to execute, for instance:

//insert a paragraph and an empty music score
if (SpInteractor spInteractor = m_pPresenter->get_interactor(0).lock())
{
string src = "<para>This is a short paragraph</para>";
SpInteractor->exec_command( new CmdInsertBlockLevelObj(src, "Add paragraph") );
src = "<ldpmusic>(score (vers 2.0)(instrument (musicData)))</ldpmusic>";
SpInteractor->exec_command( new CmdInsertBlockLevelObj(src, "Add empty music score") );
}
}

Undo/Redo

Undo/redo operations refer, respectively, to reverting the last change done to the document and to reverting the undo by re-executing the undone command.

Lomse provides full support for implementing undo/redo in your application. It maintains two queues: the history of executed commands and the list of commands that can be re-done. And Lomse provides two methods for undo/redo operations:

In addition, to facilitate that your application can enable and disable undo/redo buttons, commands and other GUI widgets related to undo/redo, Lomse provides information about the undo and redo history:

Cursor and selections

Most edition commands require a reference point in the document. For instance, a command such as delete paragraph would require a pointer to the paragraph to delete. Or a command insert note would require a pointer to the point in which the note must be inserted. For providing these references, Lomse maintains two objects: a DocCursor object and a SelectionSet object.

The DocCursor object is just a pointer to an element in the document. You can think of it as the graphic cursor in text edition applications, but in Lomse, there are two objects: the DocCursor and the Caret.

The cursor is just an internal invisible pointer; and the caret is the graphical representation of the cursor position. Lomse maintains the synchronization between the cursor and the caret, so advancing the cursor automatically forces Lomse to render the caret on the new cursor position. The cursor is always necessary, as it provides a reference point for edition commands. But the caret is just a graphical widget that your application can use or not, depending of the needs. For instance, in a batch application it would be non-sense to use a Caret! For moving the cursor to another position your application just issue specific edition commands, such as advance cursor or move cursor to start of score.

Your application can also issue edition commands for selecting and deselecting document elements, such as paragraphs, words, notes, rests, clefs, staves, etc. Object SelectionSet is just the collection of current selected elements.

The DocCursor and the SelectionSet are not parameters of the commands. These objects are maintained by Lomse and when a command is going to be executed the necessary information about target objects and context is extracted from current cursor position and/or from current set of selected objects.

And this is, basically, the document edition API. It is very simple and gives full freedom to your application for implementing the edition GUI as you'd like, or for not a having it at all!

Supported edition modes

What should be the behavior when inserting or deleting a note? For people with no previous experience with music edition software, the most intuitive behavior is one similar to what is expected from someone who comes from a pencil-and-paper background or from using a text processor: inserting/deleting a note or a rest shifts all the music that comes after it. But what about barlines? Should they be also shifted or should they remain at the same place? Each alternative has advantages and drawbacks:

  1. If barlines are also shifted, current measure could result in an irregular measure. Let's name this behavior limited ripple mode (notes are shifted but limited to current measure because barline is also shifted).
  2. If barlines remains at the same place and only notes and rests are shifted and can cross barlines boundaries and get placed in another measure, this behavior can be useful in some scenarios. For instance when you need to make one (ore more) beats completely disappear (and thus shift the remainder of the composition to the left by one beat). Or need two insert some blank space (one or more beats o whole measures), shifting to the right the rest of the music, and then you can fill in that space with what you wanted to insert. But, in general, this behavior causes a lot of trouble in music score edition. Notice that removing a note/rest in one voice and shifting all other notes in that voice implies completely altering the music in all the score, because music in shifted voice and music in the other voices and instruments are no longer synchronized. Let's name this behavior full ripple mode (notes are shifted across all the score).

To avoid problems, another possibility is to change the behavior when a note or rest is inserted or deleted. In most cases you don't need to shift music. What you need is to add notes and rests in empty measures, but in measures having content what you need is to change a few notes or rests, but always keeping measures duration and not shifting the music. Let's call this behavior replace mode (the notes are replaced and measure duration is preserved).

None of these behaviors is perfect. What should be Lomse behavior? Although the most usual behavior in music edition programs is the replace mode, the other modes can be useful in some scenarios or for some applications. As Lomse aims at not creating unnecessary constrains to your application, it was decided to support the three behaviors. For this, all edition commands related to adding or removing notes and rests require an edition mode parameter for selecting the desired behavior:

  • Limited ripple mode: when inserting or deleting notes/rests the barlines are also shifted, and this could result in irregular measures, but other measures are not affected.
  • Replace mode: Notes and rests are not inserted. Instead they overwrite the music so that measure duration is maintained. Also, notes are not deleted but replaced by rests.

For supporting full ripple mode, instead of adding more complexity to commands related to adding or removing notes and rests, it is simpler, more flexible and causes less trouble to end users to use two specific insert/delete commands that always work in full ripple mode:

  • insert blank space (beats or whole measures); and
  • delete beats/measures.

These commands accepts a parameter defining the scope: to affect only one or selected voices, one or selected staves, one or selected instruments or the whole score.

Attention
Currently Lomse does not ensure the correctness of the music notation in operations resulting in music shifts. For instance, if a quarter note crosses a barline by the duration of a removed eighth note, the quarter note should be splitted into two tied eighth notes. Or when two tied notes in different measures are shifted an are now placed in the same measure, it is no longer clear if they should continue tied or should be replaced by a longer single note. As these decisions are not trivial, Lomse does not attempt (for now) to fix these situations and therefore the commands limit the operation to inserting or deleting the requested notes or rests without more considerations.

Edition commands

This section presents a list of available commands (but more commands need to be programmed and a couple could be removed). For more information on each command read the class documentation for the command.

Document edition commands:

Selection commands:

Cursor commands: