"""
Module containing all non-spanner notations, such as glisses, bowings, fermatas, etc.
"""
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
# This file is part of SCAMP (Suite for Computer-Assisted Music in Python) #
# Copyright © 2020 Marc Evanstein <marc@marcevanstein.com>. #
# #
# This program is free software: you can redistribute it and/or modify it under the terms of #
# the GNU General Public License as published by the Free Software Foundation, either version #
# 3 of the License, or (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; #
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. #
# See the GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License along with this program. #
# If not, see <http://www.gnu.org/licenses/>. #
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
from __future__ import annotations
from abc import abstractmethod, ABC
from typing import Sequence
from xml.etree import ElementTree
from pymusicxml.score_components import Notation, MultiGliss
from pymusicxml.enums import StaffPlacement, ArpeggiationDirection
[docs]class StartGliss(Notation):
"""
Notation to attach to a note that starts a glissando
:param number: each glissando is given an id number to distinguish it from other glissandi. This must range from
1 to 6.
"""
def __init__(self, number: int = 1):
self.number = number
[docs] def render(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("slide", {"type": "start", "line-type": "solid", "number": str(self.number)}),
[docs]class StopGliss(Notation):
"""
Notation to attach to a note that ends a glissando
:param number: this should correspond to the id number of the associated :class:`StartGliss`.
"""
def __init__(self, number: int = 1):
self.number = number
[docs] def render(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("slide", {"type": "stop", "line-type": "solid", "number": str(self.number)}),
[docs]class StartMultiGliss(MultiGliss):
"""
Multi-gliss notation used for glissing multiple members of a chord
:param numbers: most natural is to pass a range object here, for the range of numbers to assign to the glisses
of consecutive chord member. However, in the case of a chord where, say, you want the upper two notes to
gliss but not the bottom, pass (None, 1, 2) to this parameter.
"""
[docs] def render(self) -> Sequence[ElementTree.Element]:
return tuple(StartGliss(n) if n is not None else None for n in self.numbers)
[docs]class StopMultiGliss(MultiGliss):
"""
End of a multi-gliss notation used for glissing multiple members of a chord.
:param numbers: These should correspond to the id numbers of the associated :class:`StartMultiGliss`.
"""
[docs] def render(self) -> Sequence[ElementTree.Element]:
return tuple(StopGliss(n) if n is not None else None for n in self.numbers)
[docs]class Fermata(Notation):
"""
Fermata notation.
:param inverted: if true, an inverted fermata
"""
def __init__(self, inverted: bool = False):
self.inverted = inverted
[docs] def render(self) -> Sequence[ElementTree.Element]:
if self.inverted:
return ElementTree.Element("fermata", {"type": "inverted"}),
else:
return ElementTree.Element("fermata"),
[docs]class Arpeggiate(Notation):
"""
Chord arpeggiation notation.
:param direction: "up" or "down"
"""
def __init__(self, direction: str | ArpeggiationDirection = None):
self.direction = ArpeggiationDirection(direction) if isinstance(direction, str) else direction
[docs] def render(self) -> Sequence[ElementTree.Element]:
if self.direction is None:
return ElementTree.Element("arpeggiate"),
else:
return ElementTree.Element("arpeggiate", {"direction": self.direction.value}),
[docs]class NonArpeggiate(Notation):
"""
Chord non-arpeggiate notation. pymusicxml only allows full chord non-arpeggiates.
"""
[docs] def render(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("non-arpeggiate", {"type": "top"}),
# --------------------------------------- Technical Notations --------------------------------------------
[docs]class Technical(Notation, ABC):
"""Abstract class for all technical notations"""
[docs] @abstractmethod
def render_technical(self) -> Sequence[ElementTree.Element]:
"""Renders the contents of the technicalelement."""
pass
[docs] def render(self) -> Sequence[ElementTree.Element]:
technical_el = ElementTree.Element("technical")
technical_el.extend(self.render_technical())
return technical_el,
[docs]class UpBow(Technical):
"""Up-bow notation"""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("up-bow"),
[docs]class DownBow(Technical):
"""Down-bow notation"""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("down-bow"),
[docs]class OpenString(Technical):
"""Open-string notation"""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("open-string"),
[docs]class Harmonic(Technical):
"""Harmonic notation.
Note: This is not the <harmony/> notation used for chord changes. This class
represents playing a note as a harmonic on a single string."""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("harmonic"),
[docs]class Stopped(Technical):
"""Stopped notation"""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("stopped"),
[docs]class SnapPizzicato(Technical):
"""Snap-pizzicato notation"""
[docs] def render_technical(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("snap-pizzicato"),
# -------------------------------------------- Ornaments -------------------------------------------------
[docs]class Ornament(Notation, ABC):
"Abstract class for all ornament notations"
[docs] @abstractmethod
def render_ornament(self) -> Sequence[ElementTree.Element]:
"""Renders the contents of the ornaments element."""
pass
[docs] def render(self) -> Sequence[ElementTree.Element]:
ornaments_el = ElementTree.Element("ornaments")
ornaments_el.extend(self.render_ornament())
return ornaments_el,
[docs]class Mordent(Ornament):
"""
Mordent ornament.
:param inverted: if true, an inverted mordent
:param placement: "above" or "below"
"""
def __init__(self, inverted: bool = False, placement: str | StaffPlacement = "above"):
self.placement = StaffPlacement(placement) if isinstance(placement, str) else placement
self.inverted = inverted
[docs] def render_ornament(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("inverted-mordent" if self.inverted else "mordent",
{"placement": self.placement.value}),
[docs]class Turn(Ornament):
"""
Turn ornament.
:param inverted: if true, an inverted turn
:param delayed: if true, a turn which is delayed until the end of the note
:param placement: "above" or "below"
"""
def __init__(self, inverted: bool = False, delayed: bool = False, placement: str | StaffPlacement = "above"):
self.placement = StaffPlacement(placement) if isinstance(placement, str) else placement
self.inverted = inverted
self.delayed = delayed
[docs] def render_ornament(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("delayed-" if self.delayed else "" + "inverted-" if self.inverted else "" + "turn",
{"placement": self.placement.value}),
[docs]class TrillMark(Ornament):
"""
Trill mark on a single note (without wavy line).
:param placement: "above" or "below"
"""
def __init__(self, placement: str | StaffPlacement = "above"):
self.placement = StaffPlacement(placement) if isinstance(placement, str) else placement
[docs] def render_ornament(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("trill-mark", {"placement": self.placement.value}),
[docs]class Schleifer(Ornament):
"""
Schleifer mark.
:param placement: "above" or "below"
"""
def __init__(self, placement: str | StaffPlacement = "above"):
self.placement = StaffPlacement(placement) if isinstance(placement, str) else placement
[docs] def render_ornament(self) -> Sequence[ElementTree.Element]:
return ElementTree.Element("schleifer", {"placement": self.placement.value}),
[docs]class Tremolo(Ornament):
"""
Tremolo lines on a note stem.
:param num_lines: number of tremolo marks on the stem. (Defaults to 3 for traditional unmeasured tremolo).
"""
def __init__(self, num_lines: int = 3):
if not 0 <= num_lines <= 8:
raise ValueError("num_lines must be between 0 and 8")
self.num_lines = num_lines
[docs] def render_ornament(self) -> Sequence[ElementTree.Element]:
tremolo_el = ElementTree.Element("tremolo")
tremolo_el.text = str(self.num_lines)
return tremolo_el,