From email, 2002 01 04

Events

An Event is the object type in Rosegarden that corresponds to a musical note. It can also represent a rest, a time signature, a tempo change, a key signature. In fact, just about anything that lives at a particular time in the score is represented as an event.

How does Event manage to represent all those different things? Well, it doesn't use polymorphism - Event is a single C++ class. It uses properties.

Properties are stored inside events in a map from property name-string to property value. They are not C++ fields, and in fact you would have to dig fairly deep in the code to get their real representation.

Property types

There are four: Int, String, Bool, and RealTimeT.

Technically, they are template specializations on 4 enumeration values. They are defined in Property.h. Other than knowing the names, you probably don't need to pay attention to that part.

  • Event::has(const PropertyName &name) returns whether an event has a value for the given property.
  • The template Event::get<PropertyType P> returns the value for the given property. There is also bool Event::get<PropertyType P>(name, &val) which sets it by reference.
  • The template Event::set<PropertyType P>(name, value) sets the given property to a given value. There is also setMaybe which sets it if it wasn't already set.

Event nominal type

An event also stores a string that declares its nominal type. This is a std::string. Its text isn't important, though it should be descriptive, but it should be unique.

  • Event::getType() returns an event's type string
  • Event::isa() returns true if the event is of the given type.
  • Some Event constructors take a type string.

Other data that event stores

Aside from the properties and type string, an Event also has an absolute time (the time in Rosegarden units – see ../data_struct/units.txt – at which the event begins), a duration (the “performance” duration of the event in the same units, so currently always zero for anything but note or rest events) and a sub-ordering value, which is used to order events that have the same absolute time (it defaults to, and most usually is, zero; but for some things like clef events it's a small negative number so as to ensure they get processed and displayed before any notes that appear at the same time). Unlike properties, the time and subordering values can only be set once, when you create the event – to modify them you must copy the event and pass new values into the copy constructor (see the header file for details, and see iterators.txt for discussion of how this affects Segment iterators that may be pointing at those events).

How to create a new sort of event

To create a new sort of event, you will need these things:

  • A unique name as a std:string
  • A unique name for each new property, as a PropertyName which is basically an interned std::string.
  • A subordering, which controls whether it is first if there are two events at the same time. It's an integer that means sorting priority. You probably shouldn't make the subordering negative, which is the realm of key signatures and time signatures.

These things are typically provided by an event adapter, but we won't get into that here.

So, for example, let's say you want a new “cow” event and you want to give it a couple of properties, say a string property for moo type (Moo, Low or Croak – cows with colds sound terrible) and a bool to indicate whether it is in fact a bull. (There are three property types; the other's int, and they're defined in Property.h.)

All you have to do to create a cow and insert it in a segment is

Event *e = new Event("cow", 1000, 0); // abs time, duration
e->set<String>("MooType", "Low");
e->set<Bool>("isBull", true);
segment->insert(e);

The strings are fairly arbitrary – you just have to ensure that nobody else has already used an event called cow or properties called MooType and isBull. Of course, you don't see much real code using literal strings; the event types are generally just declared as string constants in various places (actually almost all in NotationTypes.h), but the property names are usually declared as constant PropertyName objects (in NotationTypes, BaseProperties or notationproperties, most usually) which is a class that's constructed from a string constant but interns to an integer for faster lookup, not that you need to deal with that at all.

As another example, say you want to add a property to note events that indicates the usual fret number. To do that, you could declare a FRET_NUMBER property name in BaseProperties or notationproperties (with a value of, say, “FretNumber”) and then do stuff like

Segment::iterator i = getIteratorFromSomewhere();
Event *e = (*i);
e->set<Int>(BaseProperties::FRET_NUMBER, 4);
++i;
long fret = (*i)->get<Int>(BaseProperties::FRET_NUMBER);

The get/set code is obviously more laborious than it would be with fixed accessor methods in classes, but it's good when you haven't quite decided which properties you need, and it means you can extend other peoples' events, and it means you get I/O done for you.

Another thing is, you may notice various calls to “setMaybe” on Events as well as to “set”. This is because Events distinguish between persistent properties (which have usually been set by the user through the GUI somehow) that get streamed out to the XML file, and non-persistent ones (usually resulting from caching following some calculation, and usually possible to recalculate whenever required if necessary) that don't. The set method can take an extra argument specifying whether you're setting a persistent or non-persistent property (the default is persistent), but it's rarely used: instead to set non-persistent properties we generally use setMaybe, which sets a non-persistent property only if there is no existing persistent property of the same name. This allows the user easily to override computed values, for example in the stem-direction menu functions on the score view (the user's settings are persistent, the computed ones are not).

Uniqueness of property names

We have a big potential problem with namespacing property names, as there's no central registry of names that have been used. I've been considering adding something that might help, because we do actually intern PropertyName strings, we just don't yet complain about duplicates definitions – but it won't prevent people from using literal strings that collide. And property names are global, they aren't local to a particular event type.

Saving and restoring properties

This part is easy. You don't have to do anything special when you use a new property type. Rosegarden automatically writes all of an event's properties to the savefile, and automatically reads all of them when you restore. See XML_format

Adapters

When you adapt Event for a new purpose, it's often helpful to create an adapter class for them. If you examine the files NotationTypes.h and NotationTypes.cpp, you will notice a number of classes that adapt Event for time signature, key signature, etc.

One word: NotationTypes is already crowded. We have a policy of putting visible classes in a pair of files of their own name, but NotationTypes flagrantly violates that rule. Please don't follow our bad example.

Typically an adapter includes:

  • Fields that represent the event's data. These are generally the only non-static data fields of the dapater.
  • A std::string that names the event type. There can be more than only, but the primary one should be called EventType.
  • The subordering.
  • One constructor that constructs it just from a reference to Event.
  • A method named getAsEvent that, given a time, returns a new Event (on the heap) corresponding to that adapter's data.
 
 
dev/creating_events.txt.txt · Last modified: 2013/09/04 22:10 by tehom
Recent changes RSS feed Creative Commons License Valid XHTML 1.0 Valid CSS Driven by DokuWiki