.. _usersGuide_12_music21object: .. WARNING: DO NOT EDIT THIS FILE: AUTOMATICALLY GENERATED. PLEASE EDIT THE .py FILE DIRECTLY. User’s Guide, Chapter 12: Getting Back to Basics: The Music21Object =================================================================== Almost everything that we’ve been working with so far, ``Note`` objects, ``Chord`` objects, ``Stream`` objects, etc., are subclasses of a object that, for lack of a better name, is called :class:`~music21.base.Music21Object`. A ``Music21Object`` is something that can go in a ``Stream``, knows where it is in a Stream, and has a ``Duration`` at ``.duration``. Of course, not every object in Python is a ``Music21Object``, but a little surprisingly, not every object in ``music21`` is a “``Music21Object``”. For instance, the :class:`~music21.pitch.Pitch` object is not. If you try to put one in a ``Stream``, you’ll get an error: .. code:: ipython3 from music21 import * p = pitch.Pitch("A-2") s = stream.Stream() s.insert(0, p) :: --------------------------------------------------------------------------- StreamException Traceback (most recent call last) /var/folders/mk/qf43gd_s5f30rzzbt7l7l01h0000gn/T/ipykernel_7779/2384846781.py in 3 p = pitch.Pitch("A-2") 4 s = stream.Stream() ----> 5 s.insert(0, p) ~/git/music21base/music21/stream/base.py in insert(self, offsetOrItemOrList, itemOrNone, ignoreSort, setActiveSite) 2092 2093 # checks if element is self, among other checks -> 2094 self.coreGuardBeforeAddElement(element) 2095 # main insert procedure here 2096 ~/git/music21base/music21/stream/core.py in coreGuardBeforeAddElement(self, element, checkRedundancy) 419 'cannot insert StreamIterator into a Stream\n' 420 "Iterate over it instead (User's Guide chs. 6 and 26)") --> 421 raise StreamException( 422 f'The object you tried to add to the Stream, {element!r}, ' 423 + 'is not a Music21Object. Use an ElementWrapper object ' StreamException: The object you tried to add to the Stream, , is not a Music21Object. Use an ElementWrapper object if this is what you intend. :class:`~music21.duration.Duration`\ s are also not ``Music21Objects``: .. code:: ipython3 d = duration.Duration('half') s.insert(0, d) :: --------------------------------------------------------------------------- StreamException Traceback (most recent call last) /var/folders/mk/qf43gd_s5f30rzzbt7l7l01h0000gn/T/ipykernel_7779/777471452.py in 1 d = duration.Duration('half') ----> 2 s.insert(0, d) ~/git/music21base/music21/stream/base.py in insert(self, offsetOrItemOrList, itemOrNone, ignoreSort, setActiveSite) 2092 2093 # checks if element is self, among other checks -> 2094 self.coreGuardBeforeAddElement(element) 2095 # main insert procedure here 2096 ~/git/music21base/music21/stream/core.py in coreGuardBeforeAddElement(self, element, checkRedundancy) 419 'cannot insert StreamIterator into a Stream\n' 420 "Iterate over it instead (User's Guide chs. 6 and 26)") --> 421 raise StreamException( 422 f'The object you tried to add to the Stream, {element!r}, ' 423 + 'is not a Music21Object. Use an ElementWrapper object ' StreamException: The object you tried to add to the Stream, , is not a Music21Object. Use an ElementWrapper object if this is what you intend. Why don’t we just make everything a ``Music21Object``? There’s an overhead in making a Music21Object, so if we did that, the system would probably run about 10x slower than it does. But there’s no reason to put a ``Pitch`` or a ``Duration`` in a Stream, when a ``Note`` is basically a ``Pitch`` plus a ``Duration``. This works much better: .. code:: ipython3 n = note.Note('A-2', type='half') s.insert(0, n) s.show('text') .. parsed-literal:: :class: ipython-result {0.0} How can we tell that a ``Note`` is a ``Music21Object``? Well we can read the docs (:class:`~music21.note.Note`) where it says: **Note** ``bases``: :: * NotRest * GeneralNote * Music21Object Or we can use the ``isinstance(obj, class)`` operator on a given note. The class we are looking for is ``base.Music21Object``. We still have our A♭ as ``n``, so we can do: .. code:: ipython3 isinstance(n, base.Music21Object) .. parsed-literal:: :class: ipython-result True Note that we need to have a Note object first, we can’t test the class itself: .. code:: ipython3 isinstance(note.Note, base.Music21Object) .. parsed-literal:: :class: ipython-result False There’s one other way that you can tell if an object is a ``Music21Object``, that’s to check whether ``Music21Object`` appears in the object’s ``.classes``: .. code:: ipython3 'Music21Object' in n.classes .. parsed-literal:: :class: ipython-result True But that’s a bit of a cop-out. Things that are not ``Music21Objects`` don’t generally have a ``.classes`` property, so that won’t work: .. code:: ipython3 import datetime dt = datetime.datetime(2015, 9, 27) dt .. parsed-literal:: :class: ipython-result datetime.datetime(2015, 9, 27, 0, 0) .. code:: ipython3 'Music21Object' in dt.classes :: --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) in ----> 1 'Music21Object' in dt.classes AttributeError: 'datetime.datetime' object has no attribute 'classes' But it’s a useful and easy way of checking to see if something you know *is* a ``Music21Object`` is a specific class: .. code:: ipython3 'Chord' in n.classes .. parsed-literal:: :class: ipython-result False In fact, it’s useful enough that we’ve put it in a few objects in ``music21`` that aren’t ``Music21Objects`` .. code:: ipython3 'Duration' in d.classes .. parsed-literal:: :class: ipython-result True The class of an object cannot (well, should not) change after it’s been created. Thus it can be thought of as totally stable. Streams have many ways of filtering out ``Music21Object``\ s (a.k.a. “elements”) according to class. The easiest way is with ``.getElementsByClass``: .. code:: ipython3 s = stream.Stream() s.append(clef.TrebleClef()) s.append(meter.TimeSignature('3/4')) s.append(note.Note("A")) s.append(note.Rest()) s.append(note.Note("B")) for element in s.getElementsByClass('Note'): print(element) .. parsed-literal:: :class: ipython-result If you wanted to get the Notes and the Rest, you could figure out what their common ancestor class is and filter on that: .. code:: ipython3 note.Rest().classes .. parsed-literal:: :class: ipython-result ('Rest', 'GeneralNote', 'Music21Object', 'ProtoM21Object', 'object') .. code:: ipython3 note.Note().classes .. parsed-literal:: :class: ipython-result ('Note', 'NotRest', 'GeneralNote', 'Music21Object', 'ProtoM21Object', 'object') aha! The common ancestor class is ``GeneralNote`` so we will filter on this: .. code:: ipython3 for element in s.getElementsByClass('GeneralNote'): print(element) .. parsed-literal:: :class: ipython-result Or you could give a list of relevant classes: .. code:: ipython3 for element in s.getElementsByClass(['Note', 'Rest']): print(element) .. parsed-literal:: :class: ipython-result For those who are familiar with HTML/Javascript/DOM programming, a Python Class is most similar to a DOM Tag (like ). So DOM methods such as ``.getElementsByTagName`` are similar to ``music21``\ ’s ``.getElementsByClass``. It’s worth pointing out that there’s also a ``.classSet`` (v.2.1+) on every ``Music21Object`` that gives the string name of all base classes, the fully-qualified string name for every class, as well as the class object. Since it will return ``True`` for basically every form that someone might pass in a class selector, it’s used a lot internally for safety. It returns these objects as a ``frozenset()``, meaning it will return in any order and cannot be manipulated: .. code:: ipython3 r = note.Rest() r.classSet .. parsed-literal:: :class: ipython-result frozenset({music21.base.Music21Object, music21.note.GeneralNote, music21.note.Rest, music21.prebase.ProtoM21Object, object, 'GeneralNote', 'Music21Object', 'ProtoM21Object', 'Rest', 'builtins.object', 'music21.base.Music21Object', 'music21.note.GeneralNote', 'music21.note.Rest', 'music21.prebase.ProtoM21Object', 'object'}) .. code:: ipython3 ('Rest' in r.classSet, 'music21.note.GeneralNote' in r.classSet, base.Music21Object in r.classSet) .. parsed-literal:: :class: ipython-result (True, True, True) Attributes and Properties ------------------------- Once you know that something is a ``music21Object`` you can exploit the attributes of the object for musical purposes. ``id`` ~~~~~~ Each ``music21Object`` has a (should be) unique id stored in the ``.id`` attribute: .. code:: ipython3 n = note.Note("C#4") n.id .. parsed-literal:: :class: ipython-result 4540923128 By default, this ``.id`` is the same as the location of the object in memory, which the built-in Python function ``id()`` returns: .. code:: ipython3 id(n) .. parsed-literal:: :class: ipython-result 4540923128 But we can set it manually so that the object is easier to find later: .. code:: ipython3 n.id = 'first_note' n.id .. parsed-literal:: :class: ipython-result 'first_note' We advise ``.id`` not to include spaces or special characters, and may enforce it in the future. This ``.id`` is especially useful for ``Stream`` objects because it will be displayed in the representation of the Stream and, if there’s no other metadata, can be used as the name of the part: .. code:: ipython3 s = stream.Stream() s.id = 'empty_stream' s .. parsed-literal:: :class: ipython-result Parts can be retrieved from the ``.parts`` attribute of a score by id. .. code:: ipython3 bach = corpus.parse('bwv66.6') sopr = bach.parts['soprano'] sopr .. parsed-literal:: :class: ipython-result .. code:: ipython3 sopr.id .. parsed-literal:: :class: ipython-result 'Soprano' There are some properties such as ``.getElementById()`` that retrieve objects by ``.id``: .. code:: ipython3 s.append(n) x = s.getElementById('first_note') print(x, x.id) .. parsed-literal:: :class: ipython-result first_note If you know HTML/Javascript/DOM programming, the similarity between ``music21``\ ’s ``.getElementById()`` and HTML’s ``.getElementById()`` is intentional. Groups ~~~~~~ A group is a collection of labels for an object. Think of :class:`~music21.base.Groups` as being like ``.id`` with two differences: (1) each ``Music21Object`` can have zero, one, or multiple Groups – but it has exactly one ``.id`` and (2) a single group label can belong to multiple ``Music21Objects``. .. code:: ipython3 n.groups .. parsed-literal:: :class: ipython-result [] ``Groups`` are wrappers around lists that enforce the restriction that the label must be a string. Since they’re otherwise just lists, you can add a group to any object just by appending a string to the group: .. code:: ipython3 n.groups.append('black_key') n.groups.append('sharped') n.groups .. parsed-literal:: :class: ipython-result ['black_key', 'sharped'] We advise groups not to have spaces in them and will be enforcing this in v.3. Now we can search through Streams that ``n`` is in to find it by searching for the group ``'sharped'`` (or ``'black key'``). We use ``Stream.getElementsByGroup()``. Note that it is plural ``Elements`` while the previous call was ``getElementById`` singular. That’s because there should be only one object with each id but there could be many with the same group: .. code:: ipython3 for x in s.getElementsByGroup('sharped'): print(x, x.id) .. parsed-literal:: :class: ipython-result first_note ``Groups`` are the equivalent of the HTML/Javascript/DOM “class”. (Since ``class`` means something else in Python, we’ve changed the term). Eventually, ``Groups`` will be able to be used in styling objects automatically. For now we can just do it by hand: .. code:: ipython3 for x in s.getElementsByGroup('black_key'): x.notehead = 'circle-x' s.show() .. image:: usersGuide_12_music21object_53_0.png :width: 170px :height: 56px ActiveSite ~~~~~~~~~~ A ``Music21Object`` that is inside one or more Streams should be able to get its most recently referenced stream via its ``.activeSite`` attribute. We’ve put ``n`` in ``s``, which is called (now incorrectly) ``'empty stream'``, so n’s ``.activeSite`` should be ``s``. .. code:: ipython3 n.activeSite .. parsed-literal:: :class: ipython-result The activeSite may change over time; obviously if the note is put in another Stream then that Stream will become the activeSite. Let’s put the note in a new stream, four quarter notes from the start: .. code:: ipython3 t = stream.Stream() t.id = 'new_stream' t.insert(4.0, n) n.activeSite .. parsed-literal:: :class: ipython-result We can also change the activeSite… .. code:: ipython3 n.activeSite = s n.activeSite .. parsed-literal:: :class: ipython-result As long as it is a Stream that the Element is already a part of: .. code:: ipython3 q = stream.Stream(id='unrelated_stream') n.activeSite = q :: --------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/git/music21base/music21/stream/__init__.py in elementOffset(self, element, stringReturns) 1639 # 2.3 million times found in TestStream -> 1640 o = self._offsetDict[id(element)][0] 1641 # if returnedElement is not element: # stale reference... KeyError: 4540923128 During handling of the above exception, another exception occurred: SitesException Traceback (most recent call last) ~/git/music21base/music21/base.py in _setActiveSite(self, site) 2037 try: -> 2038 storedOffset = site.elementOffset(self) 2039 except SitesException: ~/git/music21base/music21/stream/__init__.py in elementOffset(self, element, stringReturns) 1650 'an entry for this object 0x%x is not stored in stream %s' % -> 1651 (id(element), self)) 1652 SitesException: an entry for this object 0x10ea8fcf8 is not stored in stream During handling of the above exception, another exception occurred: SitesException Traceback (most recent call last) in 1 q = stream.Stream(id='unrelated_stream') ----> 2 n.activeSite = q ~/git/music21base/music21/base.py in _setActiveSite(self, site) 2039 except SitesException: 2040 raise SitesException('activeSite cannot be set for ' -> 2041 + f'object {self} not in the Stream {site}') 2042 2043 self._activeSiteStoredOffset = storedOffset SitesException: activeSite cannot be set for object not in the Stream Newly created objects have an ``.activeSite`` of ``None`` .. code:: ipython3 m = note.Rest() m.activeSite is None .. parsed-literal:: :class: ipython-result True The ``.activeSite`` of an object will determine which other objects it is connected to, where it thinks it is, etc. The best way to demonstrate that is with the next attribute… offset ~~~~~~ The ``.offset`` of a ``Music21Object`` is the number of quarter notes from the start of the Stream it is a part of. The Stream that is referenced is the ``.activeSite``. Remember that ``n`` was inserted at offset 0 of ``s`` (``'empty stream'``) and offset 4 of ``t`` (``'new stream'``): .. code:: ipython3 n.activeSite = s n.offset .. parsed-literal:: :class: ipython-result 0.0 .. code:: ipython3 n.activeSite = t n.offset .. parsed-literal:: :class: ipython-result 4.0 If we change the offset of the ``Note`` it changes it in the ``Stream``, so that if we change the activeSite away and back, the offset is preserved. It’s more easily demonstrated than explained in words: .. code:: ipython3 n.activeSite = s n.offset = 2.0 n.activeSite = t n.activeSite = s n.offset .. parsed-literal:: :class: ipython-result 2.0 A newly created ``Music21Object`` has a great advantage – it can set its offset to anything it wants and then when it is inserted into a Stream with a single argument, it appears at that offset. .. code:: ipython3 n2 = note.Note('G-2') n2.offset = 20.0 s.insert(n2) n2.activeSite .. parsed-literal:: :class: ipython-result .. code:: ipython3 n2.offset .. parsed-literal:: :class: ipython-result 20.0 priority ~~~~~~~~ If you have a Stream with two elements at the same offset, how can you know which one of them should come first? The easiest way to ensure that one comes before the other is to change the ``.priority`` of one of them. ``.priority`` is any integer, with a default of zero. Let’s create some a new Stream and some notes: .. code:: ipython3 s = stream.Stream() d = note.Note('D4') e = note.Note('E4') s.insert(0.0, d) s.insert(0.0, e) s.show('text') .. parsed-literal:: :class: ipython-result {0.0} {0.0} Both notes are at offset 0, but D was inserted first, so it comes first. But we can move E by making it’s ``.priority`` lower than D’s: .. code:: ipython3 d.priority .. parsed-literal:: :class: ipython-result 0 .. code:: ipython3 e.priority = -1 s.show('text') .. parsed-literal:: :class: ipython-result {0.0} {0.0} .. note:: Think of `priority` like a number line. So negative numbers come before positive. Don't think of a "high priority" like 2000 meaning that you will encounter that object first. It is the opposite. Prior to music21 v3, changing the priority of an object did not automatically tell its sites that they needed to be sorted again. To get this output in earlier versions, call `s.elementsChanged()` If there is enough demand, priority may become a per-site attribute like `offset` but this is a problem we plan to tackle when need arises. If we return e’s priority to the default of ``0``, it will again appear after d: .. code:: ipython3 e.priority = 0 s.show('text') .. parsed-literal:: :class: ipython-result {0.0} {0.0} classSortOrder ~~~~~~~~~~~~~~ Objects seem to be sorted by offset first, then priority, then when they were inserted. But what about this: .. code:: ipython3 tc = clef.TrebleClef() s.insert(0.0, tc) s.show('text') .. parsed-literal:: :class: ipython-result {0.0} {0.0} {0.0} How did the Stream (correctly) know that the treble clef should come first? It’s not because of its priority: .. code:: ipython3 (tc.priority, d.priority, e.priority) .. parsed-literal:: :class: ipython-result (0, 0, 0) It’s because there is another property that aids in sorting, and that is called ``.classSortOrder``. We can see that ``c``, our treble clef, has a lower ``.classSortOrder`` than ``d`` and ``e`` by virtue of being of the class ``Clef``: .. code:: ipython3 (tc.classSortOrder, d.classSortOrder, e.classSortOrder) .. parsed-literal:: :class: ipython-result (0, 20, 20) ``.classSortOrder`` is like ``.priority`` in that lower numbers come first. We’ve arbitrarily placed Clef at ``0`` and Note at ``20`` and lots of other Classes in between. ``.classSortOrder`` is what is called a Class Attribute, meaning that the class objects have this attribute set: .. code:: ipython3 (clef.TrebleClef.classSortOrder, note.Note.classSortOrder) .. parsed-literal:: :class: ipython-result (0, 20) And any change in ``.classSortOrder`` applied to the class changes it for all its members: .. code:: ipython3 clef.TrebleClef.classSortOrder = 25 note.Note.classSortOrder = 10 (tc.classSortOrder, d.classSortOrder, e.classSortOrder) .. parsed-literal:: :class: ipython-result (25, 10, 10) But don’t do that! For one thing, the ordering of existing Streams is not changed, for another, we’ve carefully balanced the ``classSortOrder`` so that musically logical order is maintained (have you ever seen a score where the first note comes before the first clef?). So we’ll change it back here. .. code:: ipython3 clef.TrebleClef.classSortOrder = 0 note.Note.classSortOrder = 20 (tc.classSortOrder, d.classSortOrder, e.classSortOrder) .. parsed-literal:: :class: ipython-result (0, 20, 20) If you do need to do something wacky, like have notes before clefs, you can always change the ``.priority`` instead. For more information about how sorting works, jump to :ref:`Chapter 21: Ordering and Sorting of Stream Elements ` That seems like a good place for a break. We’ve got some more to cover, so I’ve split this chapter into two parts, we’ll continue in :ref:`Chapter 13: More Music21Object Attributes and Methods`