Skip to content

Commit

Permalink
various
Browse files Browse the repository at this point in the history
* Docs: extend usage.rst
* Replace references to timeline by part
* Reformat xml header in exportmusicxml
  • Loading branch information
Maarten Grachten committed Oct 4, 2019
1 parent 07e3329 commit 0a96033
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 138 deletions.
Binary file added docs/images/score_example_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/score_example_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
280 changes: 254 additions & 26 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ MusicXML file with the following contents:
.. literalinclude:: ../partitura/assets/score_example.musicxml
:language: xml

The typeset score looks like this:

.. image:: images/score_example.png
:alt: Score example
:align: center

To load the score in python we first import the partitura package:

>>> import partitura
Expand All @@ -30,11 +24,40 @@ that we load the above score as follows:
>>> my_musicxml_file = partitura.EXAMPLE_MUSICXML
>>> part = partitura.load_musicxml(my_musicxml_file)


Displaying the typeset part
===========================

The :func:`partitura.show` function displays the part as a typeset score:

>>> partitura.show(part)

.. image:: images/score_example.png
:alt: Score example
:align: center

This should open an image of the score in the default image viewing
application of your desktop.

The function requires that the `lilypond <http://lilypond.org/>`_
music typesetting program is installed on your computer.


Exporting a score to MusicXML
=============================

The :func:`partitura.save_musicxml` function exports score information to
MusicXML. The following line saves `part` to a file `mypart.musicxml`:

>>> partitura.save_musicxml(part, 'mypart.musicxml')


Viewing the musical elements
============================

The function :func:`~partitura.load_musicxml` returns the score as a :class:`~partitura.score.Part` instance. When we
print it, it displays its id and part-name:
The function :func:`~partitura.load_musicxml` returns the score as a
:class:`~partitura.score.Part` instance. When we print it, it displays its
id and part-name:

>>> print(part)
Part id="P1" name="Piano"
Expand Down Expand Up @@ -81,6 +104,7 @@ objects start or end. At `t=0` there are several starting objects, including a
:class:`~partitura.score.TimeSignature`, :class:`~partitura.score.Measure`,
:class:`~partitura.score.Page`, and :class:`~partitura.score.System`.


Extracting a piano roll
=======================

Expand Down Expand Up @@ -119,10 +143,12 @@ times:
>>> print(beat_map(pianoroll[:, 1]))
[4. 4. 4.]


Iterating over arbitrary musical objects
========================================

In the previous Section we used :attr:`part.notes <partitura.score.Part.notes>` to obtain the notes in the part as a list.
In the previous Section we used :attr:`part.notes
<partitura.score.Part.notes>` to obtain the notes in the part as a list.
This property is a short cut for the following statement:

.. doctest::
Expand All @@ -131,8 +157,8 @@ This property is a short cut for the following statement:
[<partitura.score.Note object at 0x...>, <partitura.score.Note object at 0x...>,
<partitura.score.Note object at 0x...>]

Here we access the :meth:`~partitura.score.Part.iter_all` method. Given a class,
it iterates over all instances of that class that occur in the part:
Here we access the :meth:`~partitura.score.Part.iter_all` method. Given a
class, it iterates over all instances of that class that occur in the part:

>>> for m in part.iter_all(partitura.score.Measure):
... print(m)
Expand Down Expand Up @@ -174,31 +200,233 @@ By default, `include_subclasses` is False.
>>> part = partitura.load_midi()


Creating a score by hand
========================
Creating a musical score by hand
================================

You can build a score from scratch, by creating a `Part` object:
You can build a musical score from scratch, by creating a `Part` object. We
start by renaming the `partitura.score` module to `score`, for convenience:

>>> import partitura.score as score

>>> part = score.Part('My Part')
Then we create an empty part with id 'P0' and name 'My Part' (the name is
optional, the id is mandatory), and at t=0 specify that a quarter note
duration equals a time interval of 10.

>>> part = score.Part('P0', 'My Part')
>>> part.set_quarter_duration(0, 10)
>>> ts = score.TimeSignature(3, 4)
>>> note1 = score.Note(step='A', octave=4) # A4
>>> note2 = score.Note(step='C', octave=5, alter=1) # C#5

and adding them to the part:
Adding elements to the part is done by the
:meth:`~partitura.score.Part.add` method, which takes a musical element,
a start and an end time. Either of the `start` and `end` arguments can be
omitted, but if both are omitted the method will do nothing.

We now add a 3/4 time signature at t=0, and three notes. The notes are
instantiated by specifying an (optional) id, pitch information, and an
(optional) voice:

>>> part.add(score.TimeSignature(3, 4), start=0)
>>> part.add(score.Note(id='n0', step='A', octave=4, voice=1), start=0, end=10)
>>> part.add(score.Note(id='n1', step='C', octave=5, alter=1, voice=2), start=0, end=10)
>>> part.add(score.Note(id='n2', step='C', octave=5, alter=1, voice=2), start=10, end=40)

Note that the duration of notes is not hard-coded in the Note instances, but
defined implicitly by their start and end times in the part.

Here's what the part looks like:

>>> part.add(ts, 0)
>>> part.add(note1, 0, 15)
>>> part.add(note2, 0, 20)
>>> print(part.pretty())
Part id="P0" name="My Part"
├─ TimePoint t=0 quarter=10
│ │
│ └─ starting objects
│ │
│ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ ├─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ └─ TimeSignature 3/4
├─ TimePoint t=10 quarter=10
│ │
│ ├─ ending objects
│ │ │
│ │ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ │ └─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ │
│ └─ starting objects
│ │
│ └─ Note id=n2 voice=2 staff=None type=half. pitch=C#5
└─ TimePoint t=40 quarter=10
└─ ending objects
└─ Note id=n2 voice=2 staff=None type=half. pitch=C#5

We see that the notes n0, n1, and n2 have been correctly recognized as
quarter, quarter, and dotted half, respectively.

Let's save the part to MusicXML:

>>> partitura.save_musicxml(part, 'mypart.musicxml')

When we look at the contents of `mypart.musicxml`, surprisinly, the `<part></part>` element is empty:

.. code-block:: xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.1 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise>
<part-list>
<score-part id="P0">
<part-name>My Part</part-name>
</score-part>
</part-list>
<part id="P0"/>
</score-partwise>
The problem with our newly created part is that it contains no
measures. Since the MusicXML format requires musical elements to be
contained in measures, saving the part to MusicXML omits the objects we
added.


Adding measures
===============

One option to add measures is to add them by hand like we've added the
notes and time signature. A more convenient alternative is to use the
function :func:`~partitura.score.add_measures`:

>>> score.add_measures(part)

Exporting a score to MusicXML
This function uses the time signature information in the part to add
measures accordingly:

>>> print(part.pretty())
Part id="P0" name="My Part"
├─ TimePoint t=0 quarter=10
│ │
│ └─ starting objects
│ │
│ ├─ Measure number=1
│ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ ├─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ └─ TimeSignature 3/4
├─ TimePoint t=10 quarter=10
│ │
│ ├─ ending objects
│ │ │
│ │ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ │ └─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ │
│ └─ starting objects
│ │
│ └─ Note id=n2 voice=2 staff=None type=half. pitch=C#5
├─ TimePoint t=30 quarter=10
│ │
│ ├─ ending objects
│ │ │
│ │ └─ Measure number=1
│ │
│ └─ starting objects
│ │
│ └─ Measure number=2
└─ TimePoint t=40 quarter=10
└─ ending objects
├─ Measure number=2
└─ Note id=n2 voice=2 staff=None type=half. pitch=C#5

Let' see what our part with measures looks like in typeset form:

>>> partitura.show(part)

.. image:: images/score_example_1.png
:alt: Part with measures
:align: center

Although the notes are there, the music is not typeset correctly, since the
first measure should have a duration of three quarter notes, but instead is
has a duration of four quarter notes. The problem is that the note *n2*
crosses a measure boundary, and thus should be tied.

Splitting up notes using ties
=============================

The :func:`partitura.save_musicxml` function exports score information to
MusicXML. The following statement saves `part` to a file `mypart.musicxml`:
In musical notation notes that span measure boundaries are split up, and then
tied together. This can be done automatically using the function
:func:`~partitura.score.tie_notes`:

>>> score.tie_notes(part)
>>> partitura.show(part)

.. image:: images/score_example_2.png
:alt: Part with measures
:align: center

Now the score looks correct. Displaying the contents reveals that the part
now has an extra quarter note *n2a* that starts at the measure boundary,
whereas the note *n2* is now a half note, ending at the measure boundary.

>>> print(part.pretty())
Part id="P0" name="My Part"
├─ TimePoint t=0 quarter=10
│ │
│ └─ starting objects
│ │
│ ├─ Measure number=1
│ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ ├─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ └─ TimeSignature 3/4
├─ TimePoint t=10 quarter=10
│ │
│ ├─ ending objects
│ │ │
│ │ ├─ Note id=n0 voice=1 staff=None type=quarter pitch=A4
│ │ └─ Note id=n1 voice=2 staff=None type=quarter pitch=C#5
│ │
│ └─ starting objects
│ │
│ └─ Note id=n2 voice=2 staff=None type=half tie_group=n2+n2a pitch=C#5
├─ TimePoint t=30 quarter=10
│ │
│ ├─ ending objects
│ │ │
│ │ ├─ Measure number=1
│ │ └─ Note id=n2 voice=2 staff=None type=half tie_group=n2+n2a pitch=C#5
│ │
│ └─ starting objects
│ │
│ ├─ Measure number=2
│ └─ Note id=n2a voice=2 staff=None type=quarter tie_group=n2+n2a pitch=C#5
└─ TimePoint t=40 quarter=10
└─ ending objects
├─ Measure number=2
└─ Note id=n2a voice=2 staff=None type=quarter tie_group=n2+n2a pitch=C#5


Removing elements
=================

Just like we can add elements to a part, we can also remove them, using the
:meth:`~partitura.score.Part.remove` method. The following lines remove the
measure instances that were added using the
:func:`~partitura.score.add_measures` function:

>>> for measure in part.iter_all(score.Measure):
... part.remove(measure)

>>> partitura.save_musicxml(part, 'mypart.musicxml')

6 changes: 4 additions & 2 deletions partitura/assets/score_example.musicxml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.1 Partwise//EN"
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.1 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise>
<part-list>
Expand All @@ -9,6 +9,7 @@
</score-part>
</part-list>
<part id="P1">
<!--=======================================================-->
<measure number="1">
<attributes>
<divisions>12</divisions>
Expand All @@ -17,6 +18,7 @@
<beat-type>4</beat-type>
</time>
</attributes>
<print new-page="yes" new-system="yes"/>
<note id="n01">
<pitch>
<step>A</step>
Expand Down
2 changes: 1 addition & 1 deletion partitura/exportmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

LOGGER = logging.getLogger(__name__)

DOCTYPE = '''<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">'''
DOCTYPE = '''<!DOCTYPE score-partwise PUBLIC\n "-//Recordare//DTD MusicXML 3.1 Partwise//EN"\n "http://www.musicxml.org/dtds/partwise.dtd">'''
MEASURE_SEP_COMMENT = '======================================================='

def filter_string(s):
Expand Down
Loading

0 comments on commit 0a96033

Please sign in to comment.