Skip to content

Input and Output

Input

The main function for input is readscore.read_score described below. It calls upon various file readers to read Standard MIDI Files and Music XML files. Much of the work is done by various subsystems including Music21, Partitura, and pretty_midi. Use read_score to get the recommended implementation automatically.

readscore

Functions for music data input.

Classes

Functions

set_preferred_midi_reader

set_preferred_midi_reader(reader: str = _default_midi_reader) -> str

Set a (new) preferred MIDI reader.

Returns the previous reader preference. The current preference is stored in amads.io.reader.preferred_midi_reader.

Parameters:

  • reader (str, default: _default_midi_reader ) –

    The name of the preferred MIDI reader; "music21", "pretty_midi", or "mido". Defaults to "mido".

Returns:

  • str

    The previous name of the preferred MIDI reader.

Raises:

  • ValueError

    If an invalid reader is provided.

Source code in amads/io/readscore.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def set_preferred_midi_reader(reader: str = _default_midi_reader) -> str:
    """
    Set a (new) preferred MIDI reader.

    Returns the previous reader preference. The current preference is stored
    in `amads.io.reader.preferred_midi_reader`.

    Parameters
    ----------
    reader : str, optional
        The name of the preferred MIDI reader; "music21", "pretty_midi",
        or "mido". Defaults to "mido".

    Returns
    -------
    str
        The previous name of the preferred MIDI reader.

    Raises
    ------
    ValueError
        If an invalid reader is provided.
    """
    global preferred_midi_reader
    allowed = ["music21", "pretty_midi", "mido"]
    if reader not in allowed_subsystems["midi"]:
        raise ValueError(f"Invalid MIDI reader. Must be one of {allowed}")

    previous = preferred_midi_reader
    preferred_midi_reader = reader
    return previous

set_preferred_xml_reader

set_preferred_xml_reader(reader: str = _default_xml_reader) -> str

Set a (new) preferred XML reader.

Returns the previous reader preference. The current preference is stored in amads.io.reader.preferred_xml_reader.

Parameters:

  • reader (str, default: _default_xml_reader ) –

    The name of the preferred XML reader. Can be "music21" or "partitura". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred XML reader.

Raises:

  • ValueError

    If an invalid reader is provided.

Source code in amads/io/readscore.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def set_preferred_xml_reader(reader: str = _default_xml_reader) -> str:
    """
    Set a (new) preferred XML reader.

    Returns the previous reader preference. The current preference is stored
    in `amads.io.reader.preferred_xml_reader`.

    Parameters
    ----------
    reader : str, optional
        The name of the preferred XML reader. Can be "music21" or "partitura".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred XML reader.

    Raises
    ------
    ValueError
        If an invalid reader is provided.
    """
    global preferred_xml_reader
    allowed = allowed_subsystems["musicxml"]
    if reader not in allowed:
        raise ValueError(f"Invalid XML reader. Must be one of {allowed}")

    previous = preferred_xml_reader
    preferred_xml_reader = reader
    return previous

set_preferred_kern_reader

set_preferred_kern_reader(reader: str = _default_kern_reader) -> str

Set a (new) preferred Kern reader.

Returns the previous reader preference. The current preference is stored in amads.io.reader.preferred_kern_reader.

Parameters:

  • reader (str, default: _default_kern_reader ) –

    The name of the preferred Kern reader. Can be "music21" or "partitura". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred Kern reader.

Raises:

  • ValueError

    If an invalid reader is provided.

Source code in amads/io/readscore.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def set_preferred_kern_reader(reader: str = _default_kern_reader) -> str:
    """
    Set a (new) preferred Kern reader.

    Returns the previous reader preference. The current preference is stored
    in `amads.io.reader.preferred_kern_reader`.

    Parameters
    ----------
    reader : str, optional
        The name of the preferred Kern reader. Can be "music21" or "partitura".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred Kern reader.

    Raises
    ------
    ValueError
        If an invalid reader is provided.
    """
    global preferred_kern_reader
    allowed = allowed_subsystems["kern"]
    if reader not in allowed:
        raise ValueError(f"Invalid Kern reader. Must be one of {allowed}")

    previous = preferred_kern_reader
    preferred_kern_reader = reader
    return previous

set_preferred_mei_reader

set_preferred_mei_reader(reader: str = _default_mei_reader) -> str

Set a (new) preferred MEI reader.

Returns the previous reader preference. The current preference is stored in amads.io.reader.preferred_mei_reader.

Parameters:

  • reader (str, default: _default_mei_reader ) –

    The name of the preferred MEI reader. Can be "music21" or "partitura". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred MEI reader.

Raises:

  • ValueError

    If an invalid reader is provided.

Source code in amads/io/readscore.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def set_preferred_mei_reader(reader: str = _default_mei_reader) -> str:
    """
    Set a (new) preferred MEI reader.

    Returns the previous reader preference. The current preference is stored
    in `amads.io.reader.preferred_mei_reader`.

    Parameters
    ----------
    reader : str, optional
        The name of the preferred MEI reader. Can be "music21" or "partitura".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred MEI reader.

    Raises
    ------
    ValueError
        If an invalid reader is provided.
    """
    global preferred_mei_reader
    allowed = allowed_subsystems["mei"]
    if reader not in allowed:
        raise ValueError(f"Invalid MEI reader. Must be one of {allowed}")

    previous = preferred_mei_reader
    preferred_mei_reader = reader
    return previous

set_reader_warning_level

set_reader_warning_level(level: str) -> str

Set the warning level for readscore functions.

The translation from music data files to AMADS is not always well-defined and may involve intermediate representations using Music21, Partitura or others. Usually, warnings are produced when there is possible data loss or ambiguity, but these can be more annoying than informative. The warning level can be controlled using this function, which applies to all file formats.

The current warning level is stored in amads.io.reader.reader_warning_level.

Parameters:

  • level (str) –

    The warning level to set. Options are "none", "low", "default", "high".

    • "none" will suppress all warnings during read_score() and also suppresses notice of reader subsystem and file name.
    • "low" will print one notice if there are any warnings.
    • "default" will obey environment settings to control warnings.
    • "high" will print all warnings during read_score(), overriding environment settings.

Returns:

  • str

    Previous warning level.

Raises:

  • ValueError

    If an invalid warning level is provided.

Source code in amads/io/readscore.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def set_reader_warning_level(level: str) -> str:
    """
    Set the warning level for `readscore` functions.

    The translation from music data files to AMADS is not always well-defined
    and may involve intermediate representations using Music21, Partitura or
    others. Usually, warnings are produced when there is possible data loss or
    ambiguity, but these can be more annoying than informative. The warning
    level can be controlled using this function, which applies to all file
    formats.

    The current warning level is stored in
    `amads.io.reader.reader_warning_level`.

    Parameters
    ----------
    level : str
        The warning level to set.
        Options are "none", "low", "default", "high".

        - "none" will suppress all warnings during `read_score()`
          and also suppresses notice of reader subsystem and file name.
        - "low" will print one notice if there are any warnings.
        - "default" will obey environment settings to control warnings.
        - "high" will print all warnings during `read_score()`, overriding
            environment settings.

    Returns
    -------
    str
        Previous warning level.

    Raises
    ------
    ValueError
        If an invalid warning level is provided.
    """
    global reader_warning_level
    allowed = ["none", "low", "default", "high"]
    if level not in allowed:
        raise ValueError(f"Invalid warning level. Must be one of {allowed}")

    previous = reader_warning_level
    reader_warning_level = level
    return previous

read_score

read_score(
    filename: str | Path,
    flatten: bool = False,
    collapse: bool = False,
    show: bool = False,
    format: Optional[str] = None,
    group_by_instrument: bool = True,
    ignore_hidden=False,
) -> Score

Read a file with the given format, 'musicxml', 'midi', 'kern', 'mei'.

If format is None (default), the format is based on the filename extension, which can be 'musicxml', 'mid', 'midi', 'smf', 'kern', or 'mei'. (Valid extensions are in amads.io.readscore.valid_score_extensions.)

Author: Roger B. Dannenberg

Parameters:

  • filename (str | Path) –

    The path (relative or absolute) to the music file. Can also be an URL.

  • flatten (bool, default: False ) –

    The returned score will be flat (Score, Parts, Notes).

  • collapse (bool, default: False ) –

    If collapse and flatten, the parts will be merged into one.

  • show (bool, default: False ) –

    Print a text representation of the data.

  • format (Optional[str], default: None ) –

    One from among limited standard options (e.g., 'musicxml', 'midi', 'kern', 'mei')

  • group_by_instrument (bool, default: True ) –

    If True (default), when the underlying reader (e.g. for "pretty_midi", "music21" or "partitura") reads Parts with the same instrument, their content will be grouped into a single part. This means that if flatten, then parts with the same instrument will be merged into a single part. If flatten is False, then the staffs of parts with the same instrument will be grouped within a single part. If group_by_instrument is False, the parts read in by the underlying reader will be preserved as separate parts. group_by_instrument is True by default so that when reading Piano scores with separate treble and bass staffs, the resulting AMADS Score will generally have a single Piano part with two staffs. A score for Piano and Violin will generally have two parts, one for Piano and one for Violin, as opposed to three parts (Piano-Treble, Piano-Bass, Violin). On the other hand, a score for two Violins might be represented a one part with two staffs by default, but setting group_by_instrument to False will more likely keep the two Violin parts separate. Unfortunately, exact behavior depends on the underlying reader, MIDI track names, and/or MusicXML structure and naming.

  • ignore_hidden

    If False (default), MusicXML notes marked with print-object="no" are read and marked with the "hide_on_print" property set to True. If ignore_hidden is True, these notes are omitted from the AMADS score.

Returns:

  • Score

    The imported score

Raises:

  • ValueError

    If the format is unknown or not implemented.

Note on Incomplete First Measure

In Music21, the first measure may be a partial measure containing an anacrusis (“pickup”). This is somewhat ambiguous and does not translate well to MIDI which is less expressive than MusicXML.

However, we retain the key signature and set the measure duration to the actual duration, which may be shorter in the case of pickup measures and short first endings (that normally are completed by the content of the pickup measure).

To keep timing consistent, MIDI files with pickup measures are expected to have an initial time signature that matches the actual duration, so for example a 4/4 pickup measure with only one quarter should be encoded as a 1/4 measure, and the next measure will have a new time signature of 4/4. This produces a consistent structure that MIDI editors can use to place bar lines in the correct places, but has the drawback of altering the indicated time signature in the pickup and possibly first ending measures.

General MIDI Import Notes

Each Standard MIDI File track corresponds to a Staff when creating a full AMADS Score. Everything is combined into one part when flatten and collapse are specified.

AMADS assumes that instruments (midi program numbers) are fixed for each Staff (or Part in flat scores), and MIDI channels are not represented. The use of program change messages within a track to change the program are ignored, but may generate warnings.

In general, AMADS instrument name corresponds to the MIDI track name, and MIDI program numbers are stored as "midi_program" in the info attribute of the Staff or Part corresponding to the track.

MIDI files do not have a Part/Staff structure, but you can write multiple tracks with the same name. Both the "music21" and "pretty_midi" readers will group tracks with matching names as Staffs in a single Part. This may result in an unexpected Part/Staff hierarchy if tracks are not named or if tracks are named something like "Piano-Treble" and "Piano-Bass", which would produce two Parts as different instruments as opposed to one Part with two Staffs.

Unless flatten or collapse, the MIDI file time signature information will be used to form Measures with Staffs, and Notes will be broken where they cross measure boundaries and then tied. The default time signature is 4/4.

Pretty MIDI Import Notes

If there is no program change in a file, the "pretty_midi" reader will use 0, and 0 will be stored as "midi_program" in the Part or Staff's info (see get and set).

If there is no track name, the Part.instrument is derived from the track program number (defaults to zero).

If the MIDI file track name is "Unknown", the Part.instrument is set to None. This is because when the "pretty_midi" writer writes a part where Part.instrument is None, the name "Unknown" is used instead. Therefore, the reader will recreate the AMADS Part where Part.instrument is None.

Pretty MIDI will not insert any KeySignature unless key signature meta-events are found.

Music21 MIDI Import Notes

Music21 may infer a Clef and KeySignature even though MIDI does not even have a meta-event for clefs, and even if the MIDI file has no key signature meta-event.

Music21 has an optional 8vb treble clef. This becomes simply a treble clef (`Clef.clef == "treble") in AMADS.

Some Music21 clefs are not supported in AMADS, e.g. tenor clef with an 8vb marking. These become Clef.clef == "constructed" and a tuple with (symbol, staff line, octave transposition) is stored as property "clef_info". Some unusual 18th, 19th, and 20th Century clefs are translated into more modern or conventional treble, soprano, alto, and tenor clefs, retaining the note positions on the staff, but not the original symbol.

If a MusicXML note is marked with print-object="no", the "hide_on_print" property will be set to True. You can access this with note.get("hide_on_print", False). Hidden notes as ornaments are independent of the standard ornaments described below.

Grace notes are Notes marked by setting the "is_grace" property to True. AMADS also uses the "has_slash" property (no property means False) to indicate a grace note with a slash (conventionally an acciaccatura), but Music21 incorrectly sets its slash property by default, potentially losing slash information in MusicXML, where the default is no slash.

Other Note properties are set as follows: "has_trill" and "trill_pitch" are set for Music21 Trill ("trill_pitch" has an AMADS Pitch object as its value); "has_trill_extension" is set for TrillExtension. "has_turn" and "turn_pitches" are set for Turn expressions ("turn_pitches" consists of a list with the upper pitch and the lower pitch as AMADS Pitch objects); "has_inverted_turn" and "inverted_turn_pitches" are set for InvertedTurn expressions ("inverted_turn_pitches" consists of a list with the lower pitch and the upper pitch as AMADS Pitch objects); "has_mordent" and "mordent_pitch" are set for Mordent expressions ("mordent_pitch" consists of the upper pitch as an AMADS Pitch object); "has_inverted_mordent" and "inverted_mordent_pitch" are set for InvertedMordent expressions ("inverted_mordent_pitch" consists of the upper pitch as an AMADS Pitch object); "has_shake" is set for Shake expressions; and "has_schleifer" is set for Schleifer expressions.

Source code in amads/io/readscore.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
def read_score(
    filename: str | Path,
    flatten: bool = False,
    collapse: bool = False,
    show: bool = False,
    format: Optional[str] = None,
    group_by_instrument: bool = True,
    ignore_hidden=False,
) -> Score:
    """Read a file with the given format, 'musicxml', 'midi', 'kern', 'mei'.

    If format is None (default), the format is based on the filename
    extension, which can be 'musicxml', 'mid', 'midi', 'smf', 'kern',
    or 'mei'. (Valid extensions are in
    `amads.io.readscore.valid_score_extensions`.)

    <small>**Author**: Roger B. Dannenberg</small>

    Parameters
    ----------
    filename : str | Path
        The path (relative or absolute) to the music file.
        Can also be an URL.
    flatten : bool
        The returned score will be flat (Score, Parts, Notes).
    collapse: bool
        If collapse and flatten, the parts will be merged into one.
    show : bool
        Print a text representation of the data.
    format: string
        One from among limited standard options (e.g.,
        `'musicxml'`, `'midi'`, `'kern'`, `'mei'`)
    group_by_instrument : bool
        If True (default), when the underlying reader (e.g. for "pretty_midi",
        "music21" or "partitura") reads Parts with the same instrument, their
        content will be grouped into a single part. This means that if
        `flatten`, then parts with the same instrument will be merged into a
        single part. If `flatten` is False, then the staffs of parts with the
        same instrument will be grouped within a single part.
        If `group_by_instrument` is False, the parts read in by the underlying
        reader will be preserved as separate parts. `group_by_instrument` is
        True by default so that when reading Piano scores with separate treble
        and bass staffs, the resulting AMADS Score will generally have a single
        Piano part with two staffs. A score for Piano and Violin will generally
        have two parts, one for Piano and one for Violin, as opposed to three
        parts (Piano-Treble, Piano-Bass, Violin). On the other hand, a score for
        two Violins might be represented a one part with two staffs by default,
        but setting `group_by_instrument` to False will more likely keep the
        two Violin parts separate. Unfortunately, exact behavior depends on the
        underlying reader, MIDI track names, and/or MusicXML structure and
        naming.
    ignore_hidden: bool
        If False (default), MusicXML notes marked with `print-object="no"` are
        read and marked with the "hide_on_print" property set to True.
        If `ignore_hidden` is True, these notes are omitted from the AMADS score.

    Returns
    -------
    Score
        The imported score

    Raises
    ------
    ValueError
        If the format is unknown or not implemented.

    Note on Incomplete First Measure
    --------------------------------
    In Music21, the first measure may be a partial measure containing
    an anacrusis (“pickup”). This is somewhat ambiguous and does not
    translate well to MIDI which is less expressive than MusicXML.

    However, we retain the key signature and set the measure duration
    to the actual duration, which may be shorter in the case of pickup
    measures and short first endings (that normally are completed by
    the content of the pickup measure).

    To keep timing consistent, MIDI files with pickup measures are
    expected to have an initial time signature that matches the
    actual duration, so for example a 4/4 pickup measure with only
    one quarter should be encoded as a 1/4 measure, and the next
    measure will have a new time signature of 4/4. This produces a
    consistent structure that MIDI editors can use to place bar lines
    in the correct places, but has the drawback of altering the
    indicated time signature in the pickup and possibly first ending
    measures.

    General MIDI Import Notes
    -------------------------
    Each Standard MIDI File track corresponds to a Staff when
    creating a full AMADS Score. Everything is combined into one
    part when `flatten` and `collapse` are specified.

    AMADS assumes that instruments (midi program numbers) are fixed
    for each Staff (or Part in flat scores), and MIDI channels are
    not represented. The use of program change messages within a
    track to change the program are ignored, but may generate warnings.

    In general, AMADS instrument name corresponds to the MIDI track
    name, and MIDI program numbers are stored as `"midi_program"`
    in the `info` attribute of the Staff or Part corresponding to
    the track.

    MIDI files do not have a Part/Staff structure, but you can
    write multiple tracks with the same name. Both the `"music21"`
    and `"pretty_midi"` readers will group tracks with matching
    names as Staffs in a single Part. This may result in an
    unexpected Part/Staff hierarchy if tracks are not named or
    if tracks are named something like "Piano-Treble" and
    "Piano-Bass", which would produce two Parts as different
    instruments as opposed to one Part with two Staffs.

    Unless `flatten` or `collapse`, the MIDI file time signature
    information will be used to form Measures with Staffs, and
    Notes will be broken where they cross measure boundaries and
    then tied.  The default time signature is 4/4.

    Pretty MIDI Import Notes
    ------------------------
    If there is no program change in a file, the `"pretty_midi"`
    reader will use 0, and 0 will be stored as `"midi_program"`
    in the Part or Staff's `info` (see
    [get][amads.core.basics.Event.get] and
    [set][amads.core.basics.Event.set]).

    If there is no track name, the `Part.instrument` is derived
    from the track program number (defaults to zero).

    If the MIDI file track name is `"Unknown"`, the `Part.instrument`
    is set to None. This is because when the `"pretty_midi"` writer
    writes a part where `Part.instrument is None`, the name `"Unknown"`
    is used instead. Therefore, the reader will recreate the AMADS
    Part where `Part.instrument is None`.

    Pretty MIDI will not insert any KeySignature unless key signature
    meta-events are found.

    Music21 MIDI Import Notes
    -------------------------
    Music21 may infer a Clef and KeySignature even though MIDI
    does not even have a meta-event for clefs, and even if the
    MIDI file has no key signature meta-event.

    Music21 has an optional 8vb treble clef. This becomes simply a
    treble clef (`Clef.clef == "treble") in AMADS.

    Some Music21 clefs are not supported in AMADS, e.g. tenor clef
    with an 8vb marking. These become `Clef.clef == "constructed"`
    and a tuple with (symbol, staff line, octave transposition) is
    stored as property "clef_info". Some unusual 18th, 19th, and 20th
    Century clefs are translated into more modern or conventional
    treble, soprano, alto, and tenor clefs, retaining the note
    positions on the staff, but not the original symbol.

    If a MusicXML note is marked with `print-object="no"`, the
    "hide_on_print" property will be set to True. You can access this
    with note.get("hide_on_print", False). Hidden notes as ornaments
    are independent of the standard ornaments described below.

    Grace notes are Notes marked by setting the `"is_grace"` property
    to True. AMADS also uses the `"has_slash"` property (no property
    means False) to indicate a grace note with a slash (conventionally
    an acciaccatura), but Music21 incorrectly sets its slash property
    by default, potentially losing slash information in MusicXML,
    where the default is no slash.

    Other Note properties are set as follows: `"has_trill"` and
    `"trill_pitch"` are set for Music21 Trill (`"trill_pitch"` has an
    AMADS Pitch object as its value); `"has_trill_extension"` is
    set for TrillExtension. `"has_turn"` and `"turn_pitches"` are set
    for Turn expressions (`"turn_pitches"` consists of a list with the
    upper pitch and the lower pitch as AMADS Pitch objects);
    `"has_inverted_turn"` and `"inverted_turn_pitches"` are set for
    InvertedTurn expressions (`"inverted_turn_pitches"` consists of a
    list with the lower pitch and the upper pitch as AMADS Pitch objects);
    `"has_mordent"` and `"mordent_pitch"` are set for Mordent
    expressions (`"mordent_pitch"` consists of the upper pitch as an
    AMADS Pitch object); `"has_inverted_mordent"` and
    `"inverted_mordent_pitch"` are set for InvertedMordent expressions
    (`"inverted_mordent_pitch"` consists of the upper pitch as an
    AMADS Pitch object); `"has_shake"` is set for Shake expressions;
    and `"has_schleifer"` is set for Schleifer expressions.
    """
    if isinstance(filename, str) and (
        filename.startswith("http") or "://" in filename
    ):
        with tempfile.NamedTemporaryFile(
            suffix=Path(filename).suffix or ".tmp", delete=False
        ) as tmp_file:
            urllib.request.urlretrieve(filename, tmp_file.name)
            filename = tmp_file.name

    if format is None:
        filename = Path(filename)
        ext = filename.suffix.lower()
        if ext not in [".pdf", ".ly"]:  # these are write-only extensions
            format = _suffix_to_format.get(ext)
        if not format:
            raise ValueError(
                f"Unsupported file extension: {ext}. "
                f"Valid extensions: {valid_score_extensions}"
            )

    # File format handling
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter(
            "ignore" if reader_warning_level == "none" else "always"
        )

        score = _import_score(
            filename, format, flatten, collapse, show, group_by_instrument
        )

        # Warning handling
        if reader_warning_level == "low":
            if len(w) > 0:
                print(
                    f"Warning: {len(w)} warnings were generated in "
                    f"read_score({filename}).\n"
                    "  Use amads.io.readscore.set_reader_warning_level() "
                    "for more details."
                )
        else:  # "none", "default", or "high", but w is empty if "none"
            for warning in w:
                print(
                    f"{warning.filename}:{warning.lineno}: "
                    f"{warning.category.__name__}: {warning.message}"
                )

        return score

last_used_reader

last_used_reader() -> Optional[str]

Return the name of the last used reader function.

The reader function is an internal function called by read_score and based on the format, file name, and preferences in effect.

Returns:

  • Optional[str]

    The name of the actual function used in the last call to read_score, or None if no reader has been used yet.

Source code in amads/io/readscore.py
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
def last_used_reader() -> Optional[str]:
    """Return the name of the last used reader function.

    The reader function is an internal function called by `read_score`
    and based on the format, file name, and preferences in effect.

    Returns
    -------
    Optional[str]
        The name of the actual function used in the last call to `read_score`,
        or None if no reader has been used yet.
    """
    if _last_used_reader is not None:
        return _last_used_reader.__name__
    return None

Output

Similar to input functions, you should use writescore.write_score described below to write an AMADS Score to a file.

writescore

functions for file output

Classes

Functions

set_preferred_midi_writer

set_preferred_midi_writer(writer: str = _default_midi_writer) -> str

Set a (new) preferred MIDI writer.

Returns the previous writer preference. The current preference is stored in amads.io.writer.preferred_midi_writer.

Parameters:

  • writer (str, default: _default_midi_writer ) –

    The name of the preferred MIDI writer. Can be "music21", "pretty_midi", or "mido". Defaults to "mido".

Returns:

  • str

    The previous name of the preferred MIDI writer.

Raises:

  • ValueError

    If an invalid writer is provided.

Source code in amads/io/writescore.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def set_preferred_midi_writer(writer: str = _default_midi_writer) -> str:
    """Set a (new) preferred MIDI writer.

    Returns the previous writer preference. The current preference is stored
    in `amads.io.writer.preferred_midi_writer`.

    Parameters
    ----------
    writer : str, optional
        The name of the preferred MIDI writer. Can be "music21",
        "pretty_midi", or "mido". Defaults to "mido".

    Returns
    -------
    str
        The previous name of the preferred MIDI writer.

    Raises
    ------
    ValueError
        If an invalid writer is provided.

    """
    global preferred_midi_writer
    previous_writer = preferred_midi_writer
    if writer in ["music21", "partitura", "pretty_midi", "mido"]:
        preferred_midi_writer = writer
    else:
        raise ValueError(
            "Invalid MIDI writer. Choose 'music21', 'partitura', "
            "'pretty_midi', or 'mido'."
        )
    return previous_writer

set_preferred_xml_writer

set_preferred_xml_writer(writer: str = _default_xml_writer) -> str

Set a (new) preferred XML writer.

Returns the previous writer preference. The current preference is stored in amads.io.writer.preferred_xml_writer.

Parameters:

  • writer (str, default: _default_xml_writer ) –

    The name of the preferred XML writer. Can be "music21" or "partitura". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred XML writer.

Raises:

  • ValueError

    If an invalid writer is provided.

Source code in amads/io/writescore.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def set_preferred_xml_writer(writer: str = _default_xml_writer) -> str:
    """
    Set a (new) preferred XML writer.

    Returns the previous writer preference. The current preference is stored
    in `amads.io.writer.preferred_xml_writer`.

    Parameters
    ----------
    writer : str, optional
        The name of the preferred XML writer. Can be "music21" or "partitura".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred XML writer.

    Raises
    ------
    ValueError
        If an invalid writer is provided.
    """
    global preferred_xml_writer
    previous_writer = preferred_xml_writer
    if writer in allowed_subsystems["musicxml"]:
        preferred_xml_writer = writer
    else:
        raise ValueError("Invalid XML writer. Choose 'music21' or 'partitura'.")
    return previous_writer

set_preferred_kern_writer

set_preferred_kern_writer(writer: str = _default_kern_writer) -> str

Set a (new) preferred Kern writer.

Returns the previous writer preference. The current preference is stored in amads.io.writer.preferred_kern_writer.

Parameters:

  • writer (str, default: _default_kern_writer ) –

    The name of the preferred Kern writer. Can be "music21". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred Kern writer.

Raises:

  • ValueError

    If an invalid writer is provided.

Source code in amads/io/writescore.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def set_preferred_kern_writer(writer: str = _default_kern_writer) -> str:
    """
    Set a (new) preferred Kern writer.

    Returns the previous writer preference. The current preference is stored
    in `amads.io.writer.preferred_kern_writer`.

    Parameters
    ----------
    writer : str, optional
        The name of the preferred Kern writer. Can be "music21".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred Kern writer.

    Raises
    ------
    ValueError
        If an invalid writer is provided.
    """
    global preferred_kern_writer
    previous_writer = preferred_kern_writer
    if writer in allowed_subsystems["kern"]:
        preferred_kern_writer = writer
    else:
        raise ValueError("Invalid Kern writer. Choose 'music21'.")
    return previous_writer

set_preferred_mei_writer

set_preferred_mei_writer(writer: str = _default_mei_writer) -> str

Set a (new) preferred MEI writer.

Returns the previous writer preference. The current preference is stored in amads.io.writer.preferred_mei_writer.

Parameters:

  • writer (str, default: _default_mei_writer ) –

    The name of the preferred MEI writer. Can be "music21". Defaults to "music21".

Returns:

  • str

    The previous name of the preferred MEI writer.

Raises:

  • ValueError

    If an invalid writer is provided.

Source code in amads/io/writescore.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def set_preferred_mei_writer(writer: str = _default_mei_writer) -> str:
    """
    Set a (new) preferred MEI writer.

    Returns the previous writer preference. The current preference is stored
    in `amads.io.writer.preferred_mei_writer`.

    Parameters
    ----------
    writer : str, optional
        The name of the preferred MEI writer. Can be "music21".
        Defaults to "music21".

    Returns
    -------
    str
        The previous name of the preferred MEI writer.

    Raises
    ------
    ValueError
        If an invalid writer is provided.
    """
    global preferred_mei_writer
    previous_writer = preferred_mei_writer
    if writer in allowed_subsystems["mei"]:
        preferred_mei_writer = writer
    else:
        raise ValueError("Invalid MEI writer. Choose 'music21'.")
    return previous_writer

set_preferred_pdf_writer

set_preferred_pdf_writer(writer: str = _default_pdf_writer) -> str

Set a (new) preferred PDF writer.

Returns the previous writer preference. The current preference is stored in amads.io.writescore.preferred_pdf_writer. Preferences are: - "music21-lilypond" - use music21 to create a LilyPond file, then use LilyPond to create a PDF. - "music21-xml-lilypond" - use music21 to create a MusicXML file, then run the program musicxml2ly to convert XML to LilyPond, then run LilyPond to create a PDF. - "partitura-xml-lilypond" - use partitura to create a MusicXML file, then run the program musicxml2ly to convert XML to LilyPond, then run LilyPond to create a PDF.

Parameters:

  • writer (str, default: _default_pdf_writer ) –

    The name of the preferred PDF writer. Can be "music21-lilypond", "music21-xml-lilypond", or "partitura-xml-lilypond". Defaults to "music21-xml-lilypond".

Returns:

  • str

    The previous name of the preferred PDF writer.

Raises:

  • ValueError

    If an invalid writer is provided.

Source code in amads/io/writescore.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def set_preferred_pdf_writer(writer: str = _default_pdf_writer) -> str:
    """
    Set a (new) preferred PDF writer.

    Returns the previous writer preference. The current preference is stored
    in `amads.io.writescore.preferred_pdf_writer`. Preferences are:
    - "music21-lilypond" - use music21 to create a LilyPond file, then use
      LilyPond to create a PDF.
    - "music21-xml-lilypond" - use music21 to create a MusicXML file, then run
      the program musicxml2ly to convert XML to LilyPond, then run LilyPond to
      create a PDF.
    - "partitura-xml-lilypond" - use partitura to create a MusicXML file, then
      run the program musicxml2ly to convert XML to LilyPond, then run LilyPond
      to create a PDF.


    Parameters
    ----------
    writer : str, optional
        The name of the preferred PDF writer. Can be "music21-lilypond",
        "music21-xml-lilypond", or "partitura-xml-lilypond".
        Defaults to "music21-xml-lilypond".

    Returns
    -------
    str
        The previous name of the preferred PDF writer.

    Raises
    ------
    ValueError
        If an invalid writer is provided.
    """
    global preferred_pdf_writer
    previous_writer = preferred_pdf_writer
    if writer in allowed_subsystems["pdf"]:
        preferred_pdf_writer = writer
    else:
        raise ValueError(
            "Invalid PDF writer. Choose " f"{allowed_subsystems['pdf']}."
        )
    return previous_writer

set_writer_warning_level

set_writer_warning_level(level: str) -> str

Set the warning level for writescore functions.

The translation from AMADS to music data files is not always well-defined and may involve intermediate representations using Music21, Partitura or others. Usually, warnings are produced when there is possible data loss or ambiguity, but these can be more annoying than informative. The warning level can be controlled using this function, which applies to all file formats.

The current warning level is stored in amads.io.writer.writer_warning_level.

Parameters:

  • level (str) –

    The warning level to set. Can be "none", "low", "default", "high".

    • "none" - will suppress all warnings during write_score().
    • "low" - will show print one notice if there are any warnings.
    • "default" - will obey environment settings to control warnings.
    • "high" - will print all warnings during write_score(), overriding environment settings.

Returns:

  • str

    Previous warning level.

Raises:

  • ValueError

    If an invalid warning level is provided.

Source code in amads/io/writescore.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
def set_writer_warning_level(level: str) -> str:
    """
    Set the warning level for writescore functions.

    The translation from AMADS to music data files is not always well-defined
    and may involve intermediate representations using Music21, Partitura or
    others. Usually, warnings are produced when there is possible data loss or
    ambiguity, but these can be more annoying than informative. The warning
    level can be controlled using this function, which applies to all file
    formats.

    The current warning level is stored in
    `amads.io.writer.writer_warning_level`.

    Parameters
    ----------
    level : str
        The warning level to set. Can be "none", "low", "default", "high".

        - "none" - will suppress all warnings during write_score().
        - "low" - will show print one notice if there are any warnings.
        - "default" - will obey environment settings to control warnings.
        - "high" - will print all warnings during write_score(), overriding
            environment settings.

    Returns
    -------
    str
        Previous warning level.

    Raises
    -------
    ValueError
        If an invalid warning level is provided.
    """
    global writer_warning_level
    previous_level = writer_warning_level
    if level in ["none", "low", "default", "high"]:
        writer_warning_level = level
    else:
        raise ValueError(
            "Invalid warning level. Choose 'none', 'low', 'default', or 'high'."
        )
    return previous_level

write_score

write_score(
    score: Score,
    filename: str | Path | None,
    show: bool = False,
    format: Optional[str] = None,
    is_temp: bool = False,
    minimum_duration: float = 0.0,
) -> Path

Write a file with the given format.

If format is None (default), the format is based on the filename extension, which can be one of writescore.valid_score_extensions ('xml', 'musicxml', 'mxl', 'mid', 'midi', 'smf', 'krn', 'kern', 'mei', 'pdf', or 'ly').

Author: Roger B. Dannenberg

Parameters:

  • score (Score) –

    the score to write

  • filename (str | Path | None) –

    the path (relative or absolute) to the music file. If None, a temp directory is created, a file is created in the directory, and the path of the written file is returned.

  • show (bool, default: False ) –

    print a text representation of the data

  • format (Optional[string], default: None ) –

    one of 'musicxml', 'midi', 'kern', 'mei', 'pdf', 'lilypond'. Defaults to the format implied by filename.

  • is_temp (bool, default: False ) –

    If true, then intermediate files needed to construct filename can be placed in the same directory and named by changing the extension because filename is in a unique temp directory created by _path_help. (This is merely an optimization to group temp files and avoid creating another temp directory when it is unnecessary.)

  • minimum_duration (float, default: 0.0 ) –

    If greater than 0, then notes in MIDI file output will be extended to at least this duration. This is useful when graces notes are encoded with zero duration, but you want them to be visible and audible as MIDI. Non-MIDI output formats will ignore this parameter.

Returns:

  • Path

    The path to which the data was written.

Raises:

  • ValueError

    if format is unknown

Notes

AMADS assumes that instruments (midi program numbers) are fixed for each Staff (or Part in flat scores), and MIDI channels are not represented. This corresponds to some DAWs such as LogicPro, which represents channels but ignores them when tracks are synthesized in software by a single instrument. The MIDI program is stored as info (see get and set) under key "midi_program" on the Staff, or if there is no Staff or no "midi_program" on the Staff, under key "midi_program" on the Part.

Parts also have an instrument attribute, which is stored as the MIDI track name. (Therefore, if a Part has two Staffs, there will be two tracks with the same name.) If there is no MIDI program for the track, the 'pretty_midi' writer will use pretty_midi.instrument_name_to_program to determine a program number since a program number is required. (As opposed to Standard MIDI Files, which need not have any MIDI program message at all.) If pretty_midi.instrument_name_to_program fails, the program is set to 0 (“Acoustic Grand Piano”).

Partitura does not seem to support per-staff key signatures, so key signatures from AMADS are simply added to Partitura parts. When there are multiple staffs, there could be duplicate key signatures (to be tested).

Pretty MIDI also requires an instrument name. If the AMADS Part instrument attribute is None, then "Unknown" is used. The Pretty MIDI reader will convert "Unknown" back to None.

Music21 output to MusicXML can contain ornaments, which are indicated by special properties on Notes. See read_score documentation for a description of ornament properties. Some Music21 ornaments are described in terms of accidentals rather than actual pitches, which means AMADS must sometimes use a generate-and-test approach to finding the correct accidental, given that the actual pitch is determined by both the accidental label and the current key signature. An exception is raised if the ornament cannot be expressed in Music21.

Music21 output to MusicXML will set print-object="no" if Note.get("hide_on_print", False) is True. This property is set by the Music21-based MusicXML reader.

Source code in amads/io/writescore.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def write_score(
    score: Score,
    filename: str | Path | None,
    show: bool = False,
    format: Optional[str] = None,
    is_temp: bool = False,
    minimum_duration: float = 0.0,
) -> Path:
    """Write a file with the given format.

    If format is None (default), the format is based on the filename
    extension, which can be one of `writescore.valid_score_extensions`
    ('xml', 'musicxml', 'mxl', 'mid', 'midi', 'smf', 'krn', 'kern',
    'mei', 'pdf', or 'ly').

    <small>**Author**: Roger B. Dannenberg</small>

    Parameters
    ----------
    score : Score
        the score to write
    filename : str | Path | None
        the path (relative or absolute) to the music file. If None, a temp
        directory is created, a file is created in the directory, and the
        path of the written file is returned.
    show : bool
        print a text representation of the data
    format : Optional[string]
        one of `'musicxml'`, `'midi'`, `'kern'`, `'mei'`, `'pdf'`, `'lilypond'`.
        Defaults to the format implied by `filename`.
    is_temp: bool
        If true, then intermediate files needed to construct filename can
        be placed in the same directory and named by changing the extension
        because filename is in a unique temp directory created by _path_help.
        (This is merely an optimization to group temp files and avoid creating
        another temp directory when it is unnecessary.)
    minimum_duration: float
        If greater than 0, then notes in MIDI file output will be extended
        to at least this duration. This is useful when graces notes are
        encoded with zero duration, but you want them to be visible and
        audible as MIDI. Non-MIDI output formats will ignore this parameter.

    Returns
    -------
    Path
        The path to which the data was written.

    Raises
    ------
    ValueError
        if format is unknown

    Notes
    -----
    AMADS assumes that instruments (midi program numbers) are fixed
    for each Staff (or Part in flat scores), and MIDI channels are
    not represented. This corresponds to some DAWs such as LogicPro,
    which represents channels but ignores them when tracks are
    synthesized in software by a single instrument. The MIDI program
    is stored as info (see [get][amads.core.basics.Event.get] and
    [set][amads.core.basics.Event.set]) under key `"midi_program"`
    on the Staff, or if there is no Staff or no `"midi_program"` on
    the Staff, under key `"midi_program"` on the Part.

    Parts also have an `instrument` attribute, which is stored as
    the MIDI track name. (Therefore, if a Part has two Staffs, there
    will be two tracks with the same name.)  If there is no MIDI
    program for the track, the `'pretty_midi'` writer will use
    `pretty_midi.instrument_name_to_program` to determine a program
    number since a program number is required. (As opposed to Standard
    MIDI Files, which need not have any MIDI program message at all.)
    If `pretty_midi.instrument_name_to_program` fails, the program is
    set to 0 (“Acoustic Grand Piano”).

    Partitura does not seem to support per-staff key signatures,
    so key signatures from AMADS are simply added to Partitura
    parts. When there are multiple staffs, there could be
    duplicate key signatures (to be tested).

    Pretty MIDI also requires an instrument name. If the AMADS Part
    `instrument` attribute is `None`, then `"Unknown"` is used. The
    Pretty MIDI reader will convert `"Unknown"` back to `None`.

    Music21 output to MusicXML can contain ornaments, which are
    indicated by special properties on Notes. See `read_score`
    documentation for a description of ornament properties. Some
    Music21 ornaments are described in terms of accidentals rather
    than actual pitches, which means AMADS must sometimes use a
    generate-and-test approach to finding the correct accidental,
    given that the actual pitch is determined by both the accidental
    label and the current key signature. An exception is raised if
    the ornament cannot be expressed in Music21.

    Music21 output to MusicXML will set `print-object="no"` if
    Note.get("hide_on_print", False) is True. This property is
    set by the Music21-based MusicXML reader.

    """
    if not isinstance(filename, Path):
        if filename:
            filename = Path(filename)
        elif format:
            tmp_dir = mkdtemp("_amads")
            filename = Path(tmp_dir) / ("score" + _format_to_suffix[format])
    format = _update_format_with_filename(format, cast(Path, filename))

    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter(
            "ignore" if writer_warning_level == "none" else "always"
        )
        # format is guaranteed to be a Path here
        _export_score(score, filename, cast(str, format), show)  # type: ignore

        # Warning handling
        if writer_warning_level == "low":
            if len(w) > 0:
                print(
                    f"Warning: {len(w)} warnings were generated in"
                    f" write_score({filename}). Use"
                    " amads.io.writescore.set_writer_warning_level() for"
                    " more details."
                )
        else:  # "none", "default", or "high"
            for warning in w:
                formatted = warnings.formatwarning(
                    warning.message,
                    warning.category,
                    warning.filename,
                    warning.lineno,
                )
                print(formatted, end="")
    return cast(Path, filename)

last_used_writer

last_used_writer() -> Optional[str]

Return the name of the last used writer function.

The writer function is an internal function called by write_score and based on the format, file name, and preferences in effect.

Returns:

  • Optional[str]

    The name of the actual function used in the last call to write_score, or None if no writer has been used yet.

Source code in amads/io/writescore.py
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def last_used_writer() -> Optional[str]:
    """Return the name of the last used writer function.

    The writer function is an internal function called by `write_score`
    and based on the format, file name, and preferences in effect.

    Returns
    -------
    Optional[str]
        The name of the actual function used in the last call to `write_score`,
        or None if no writer has been used yet.
    """
    if _last_used_writer is not None:
        return _last_used_writer.__name__
    return None

Display

Similar to output functions, you should use displayscore.display_score described below to display an AMADS Score. You can use Music21 to write directly to a LilyPond file and use LilyPond to render the file as a PDF, you can use Music21 or Partitura to write a musicxml file, convert that with musicxml2ly and render with LilyPond, you can write a musicxml file and open it with MuseScore, or you can write a musicxml file and embed it in an HTML file along with Open Sheet Music Display (OSDM) and open it in a browser.

You will need to install LilyPond and/or MuseScore to use them for music display. OSDM is installed automatically as part of AMADS.

displayscore

Functions for score display

Author: Roger B. Dannenberg

Classes

Functions

set_preferred_display_method

set_preferred_display_method(
    method: str = _default_display_method,
) -> str

Set a (new) preferred display method.

Returns the previous preference. The current preference is stored in amads.io.writer.preferred_display_method

Parameters:

  • method (str, default: _default_display_method ) –

    The name of the preferred method. Can be "pdf", "musescore", "OSMD" (Open Sheet Music Display) or "pianoroll". Note that if the method is "pdf", then io.writescore.preferred_pdf_writer is used to create a PDF to display. Defaults to "pdf".

Returns:

  • str

    The previous name of the preferred method.

Raises:

  • ValueError

    If an invalid method is provided.

Source code in amads/io/displayscore.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def set_preferred_display_method(method: str = _default_display_method) -> str:
    """Set a (new) preferred display method.

    Returns the previous preference. The current preference is stored
    in `amads.io.writer.preferred_display_method`

    Parameters
    ----------
    method : str
        The name of the preferred method. Can be "pdf", "musescore", "OSMD"
        (Open Sheet Music Display) or "pianoroll". Note that if the method
        is "pdf", then `io.writescore.preferred_pdf_writer` is used to create
        a PDF to display. Defaults to "pdf".

    Returns
    -------
    str
        The previous name of the preferred method.

    Raises
    ------
    ValueError
        If an invalid method is provided.
    """
    global preferred_display_method
    previous_display_method = preferred_display_method
    if method in ["pdf", "musescore", "OSMD", "pianoroll"]:
        preferred_display_method = method
    else:
        raise ValueError(
            "Invalid method. Choose 'pdf', 'musescore', 'OSMD', or 'pianoroll'."
        )
    return previous_display_method

display_file

display_file(file: str) -> None

Display a score from a file path.

Parameters:

  • file (str) –

    the file (must be MIDI or MusicXML) to display.

Raises:

  • RunTimeError

    If file does not end with ".mid", ".midi", ".xml", ".musicxml" or ".mxl".

Source code in amads/io/displayscore.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def display_file(file: str) -> None:
    """Display a score from a file path.

    Parameters
    ----------
    file : str
        the file (must be MIDI or MusicXML) to display.

    Raises
    ------
    RunTimeError
        If `file` does not end with ".mid", ".midi", ".xml", ".musicxml"
        or ".mxl".
    """
    if file.endswith(".mid") or file.endswith(".midi"):
        score = read_score(file)
        pianoroll(score)
    elif (
        file.endswith(".xml")
        or file.endswith(".musicxml")
        or file.endswith(".mxl")
    ):
        _display_musicxml_file(file)
    elif file.endswith(".pdf"):
        if suppress_external_open():
            print(f"PDF display suppressed during tests: {file}.")
        else:
            webbrowser.open(Path(file).resolve().as_uri())  # type: ignore
    else:
        raise RuntimeError(
            f"Unsupported file extension for display_file: {file}"
        )

display_score

display_score(score: Score, show: bool = False) -> None

Display a score.

AMADS supports several display methods. "pdf" (default) uses the preferred MusicXML writer (defaults to Music21) to write an XML file. Then, Lilypond (which must be installed) is run to create a pdf file. The pdf file is then opened using Python's webbrowser.open, which may in fact open the pdf with Preview on MacOS.

"pianoroll" will make a pianoroll display directly from the score and plot it, showing the plot.

"musescore" will make a MusicXML file using the preferred MusicXML writer and open the file with MuseScore, which must be installed.

"OSMD" will make a MusicXML file using the preferred MusicXML writer. It then constructs a web page consisting of the MusicXML file (as a Javascript text string) and OSMD (the Open Sheet Music Display library which runs in the browser to render the MusicXML). The constructed HTML file is opened in a browser.

Parameters:

  • score (Score) –

    the score to write

  • show (bool, default: False ) –

    show text representation of converted score for debugging.

Raises:

  • RunTimeError

    If the preferred_display_method is not "pianoroll", "musescore", or "OSMD".

Source code in amads/io/displayscore.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def display_score(score: Score, show: bool = False) -> None:
    """Display a score.

    AMADS supports several display methods. "pdf" (default) uses the
    preferred MusicXML writer (defaults to Music21) to write an XML file.
    Then, Lilypond (which must be installed) is run to create a pdf file.
    The pdf file is then opened using Python's `webbrowser.open`, which
    may in fact open the pdf with Preview on MacOS.

    "pianoroll" will make a pianoroll display directly from the score
    and plot it, showing the plot.

    "musescore" will make a MusicXML file using the preferred MusicXML
    writer and open the file with MuseScore, which must be installed.

    "OSMD" will  make a MusicXML file using the preferred MusicXML
    writer. It then constructs a web page consisting of the MusicXML
    file (as a Javascript text string) and OSMD (the Open Sheet
    Music Display library which runs in the browser to render the
    MusicXML). The constructed HTML file is opened in a browser.

    Parameters
    ----------
    score : Score
        the score to write
    show : bool
        show text representation of converted score for debugging.

    Raises
    ------
    RunTimeError
        If the `preferred_display_method` is not "pianoroll", "musescore",
        or "OSMD".
    """
    if preferred_display_method == "pdf":
        # we need a temp directory for intermediate files:
        pdf_file = writescore._path_help(None, ".pdf")
        write_score(score, str(pdf_file), show, "pdf", True)
        display_file(str(pdf_file))
    elif preferred_display_method == "pianoroll":
        pianoroll(score)  # Note that 'show' for pianoroll invokes plt.show(),
        # which is different from display_score's 'show' argument, which prints
        # a text representation of the score upon conversion. However,
        # pianoroll does not *do* conversion, so our 'show' argument is not
        # relevant here. pianoroll's 'show' argument is set to True by default.
    elif (
        preferred_display_method == "musescore"
        or preferred_display_method == "OSMD"
    ):
        with tempfile.NamedTemporaryFile(
            prefix="amads_display_", suffix=".musicxml", delete=False
        ) as tmp_file:
            xml_path = tmp_file.name

        write_score(score, xml_path, show, "musicxml")

        if suppress_external_open():
            print(f"score display suppressed during tests; wrote {xml_path}")
            return
        _display_musicxml_file(xml_path)
    else:
        raise RuntimeError(
            f"Unsupported display method: {preferred_display_method}"
        )

Piano Roll Display

pianoroll

Plot a score using a piano roll visualization.

Based on pianoroll in Matlab MIDI Toolbox. Original documentaion is here., page 83.

Classes

Functions

pianoroll

pianoroll(
    score: Score,
    title: str = "Piano Roll",
    y_label: str = "name",
    x_label: str = "quarter",
    color: str = "skyblue",
    accidental: str = "sharp",
    show: bool = True,
) -> Figure

Converts a Score to a piano roll display of a musical score.

Based on the pianoroll function in Matlab MIDItoolbox.

You can also create a piano roll display with set_preferred_display_method("pianoroll") and calling display_score().

Parameters:

  • score (Score) –

    The musical score to display

  • title (str, default: 'Piano Roll' ) –

    The title of the plot. Defaults to "Piano Roll".

  • y_label (str, default: 'name' ) –

    Determines whether the y-axis is labeled with note names or MIDI numbers. Valid Input: 'name' (default) or 'num'.

  • x_label (str, default: 'quarter' ) –

    Determines whether the x-axis is labeled with quarters or seconds. Valid input: 'quarter' (default) or 'sec'.

  • color (str, default: 'skyblue' ) –

    The color of the note rectangles. Defaults to 'skyblue'.

  • accidental (str, default: 'sharp' ) –

    Determines whether the y-axis is labeled with sharps or flats. Only useful if argument y_label is 'name'. Raises exception on inputs that's not 'sharp', 'flat', or 'both'. Defaults to 'sharp', which is what is done in miditoolbox. 'both' means use AMADS defaults which are C#, Eb, F#, G#, Bb.

  • show (bool, default: True ) –

    If True (default), the plot is displayed.

Returns:

  • Figure

    A matplotlib.figure.Figure of a pianoroll diagram.

Raises:

  • ValueError

    If there are invalid input arguments

Source code in amads/io/pianoroll.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def pianoroll(
    score: Score,
    title: str = "Piano Roll",
    y_label: str = "name",
    x_label: str = "quarter",
    color: str = "skyblue",
    accidental: str = "sharp",
    show: bool = True,
) -> figure.Figure:
    """Converts a Score to a piano roll display of a musical score.

    Based on the pianoroll function in Matlab MIDItoolbox.

    You can also create a piano roll display with
    `set_preferred_display_method("pianoroll")` and calling `display_score()`.

    Parameters
    ----------
    score : Score
        The musical score to display
    title : str, optional
        The title of the plot. Defaults to "Piano Roll".
    y_label : str, optional
        Determines whether the y-axis is
        labeled with note names or MIDI numbers.
        Valid Input: 'name' (default) or 'num'.
    x_label : str, optional
        Determines whether the x-axis is labeled with quarters or
        seconds. Valid input: 'quarter' (default) or 'sec'.
    color : str, optional
        The color of the note rectangles. Defaults to 'skyblue'.
    accidental : str, optional
        Determines whether the y-axis is
        labeled with sharps or flats. Only useful if argument
        y_label is 'name'. Raises exception on inputs that's not
        'sharp', 'flat', or 'both'. Defaults to 'sharp', which is
        what is done in miditoolbox. 'both' means use AMADS defaults
        which are C#, Eb, F#, G#, Bb.
    show : bool, optional
        If True (default), the plot is displayed.

    Returns
    -------
    Figure
        A matplotlib.figure.Figure of a pianoroll diagram.

    Raises
    ------
    ValueError
        If there are invalid input arguments
    """

    # remove ties and make a sorted list of all notes:
    score = score.flatten(collapse=True)
    # Check for correct x_label input argument
    if x_label != "quarter" and x_label != "sec":
        raise ValueError("Invalid x_label type")

    # Check for correct accidental input argument
    if accidental != "sharp" and accidental != "flat" and accidental != "both":
        raise ValueError("Invalid accidental type")

    fig, ax = plt.subplots()

    min_note, max_note = 127.0, 0.0
    max_time = 1  # plot at least 1 second or beat
    # now score has one part that is all notes
    for note in cast(Part, next(score.find_all(Part))).content:
        note = cast(Note, note)
        onset_time = note.onset
        offset_time = note.offset
        pitch = note.key_num - 0.5  # to center note rectangle

        # Conditionally converts beat to sec
        if x_label == "sec" and score.units_are_quarters:
            onset_time = score.time_map.quarter_to_time(onset_time)
            offset_time = score.time_map.quarter_to_time(offset_time)
        elif x_label == "quarter" and score.units_are_seconds:
            onset_time = score.time_map.time_to_quarter(onset_time)
            offset_time = score.time_map.time_to_quarter(offset_time)
        # Stores min and max note for y_axis labeling
        if pitch < min_note:
            min_note = pitch
        if pitch > max_note:
            max_note = pitch

        # Stores max note start time + note duration for x_axis limit
        if offset_time > max_time:
            max_time = offset_time

        # Draws the note
        rect = patches.Rectangle(
            (onset_time, pitch),
            offset_time - onset_time,
            1,
            edgecolor="black",
            facecolor=color,
        )
        ax.add_patch(rect)

    # Determines correct axis labels
    if min_note == 127 and max_note == 0:  # "fake" better axes:
        min_note = 59
        max_note = 59

    midi_numbers = list(range(int(min_note), int(max_note + 2)))

    match y_label:
        case "num":
            notes = midi_numbers
            y_label = "MIDI Key (Pitch) Number"
        case "name":
            if accidental == "both":
                accidental = "default"  # for simplest_enharmonic
            notes = [
                Pitch(mn).simplest_enharmonic(accidental).name_with_octave
                for mn in midi_numbers
            ]
            y_label = "Pitch Name"
        case _:
            raise ValueError("Invalid y_label type")

    # Plots the graph
    ax.set_title(title)

    ax.set_xlabel("Quarters" if x_label == "quarter" else "Seconds")
    ax.set_ylabel(y_label)

    ax.set_yticks(midi_numbers)
    ax.set_yticklabels([str(note) for note in notes])

    ax.set_xlim(0, max_time)
    ax.set_ylim(min(midi_numbers), max(midi_numbers) + 1)

    ax.grid(True)

    if show:
        plt.show()

    return fig

Built-In Scores

example

Functions

fullpath

fullpath(example: str) -> str

Construct a full path name for an example file.

For example, fullpath("midi/sarabande.mid") returns a path to a readable file from this package. This uses importlib so that we can read files even from compressed packages (we hope).

Parameters:

  • example (str) –

    The relative path to the example file, starting from the "music" directory. For example, "midi/sarabande.mid" or "musicxml/ex2.xml".

Returns:

  • str

    The full path to the example file.

Raises:

  • FileNotFoundError

    If the example file is not found or is not readable.

Source code in amads/music/example.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def fullpath(example: str) -> str:
    """Construct a full path name for an example file.

    For example, fullpath("midi/sarabande.mid") returns a path to a
    readable file from this package.  This uses importlib so that
    we can read files even from compressed packages (we hope).

    Parameters
    ----------
    example : str
        The relative path to the example file, starting from the "music"
        directory. For example, "midi/sarabande.mid" or "musicxml/ex2.xml".

    Returns
    -------
    str
        The full path to the example file.

    Raises
    ------
    FileNotFoundError
        If the example file is not found or is not readable.
    """

    def trim_path(full: str) -> str:
        """remove first part of path to construct valid parameter value"""
        first_part = "amads/music/"
        index = full.find(first_part)
        return full if index == -1 else full[index + len(first_part) :]

    path = str(resources.files("amads").joinpath("music/" + example))

    if os.path.isfile(path) and os.access(path, os.R_OK):
        return path

    print("In amads.example.fullpath(" + example + "):")
    print("    File was not found. Try one of these:")

    spec = util.find_spec("amads")
    if spec is None:
        print("Error: Package amads not found")
        raise FileNotFoundError("Package amads not found")
    if spec.submodule_search_locations is None:
        print("Error: Package amads has no submodule search locations")
        raise FileNotFoundError(
            "Package amads has no submodule search locations"
        )
    package_path = spec.submodule_search_locations[0]

    # Walk through the directory hierarchy
    for root, dirs, files in os.walk(package_path):
        for file in files:
            for ext in valid_score_extensions:
                if file.endswith(ext):
                    parameter_option = trim_path(os.path.join(root, file))
                    print(f'   "{parameter_option}"')
    raise FileNotFoundError("Example file not found")