# ------------------------------------------------------------------------------
# Name: note.py
# Purpose: music21 classes for representing notes
#
# Authors: Michael Scott Asato Cuthbert
# Christopher Ariza
#
# Copyright: Copyright © 2006-2024 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Classes and functions for creating Notes, Rests, and Lyrics.
The :class:`~music21.pitch.Pitch` object is stored within,
and used to configure, :class:`~music21.note.Note` objects.
'''
from __future__ import annotations
import copy
import typing as t
from typing import overload # PyCharm bug
import unittest
from music21 import base
from music21 import beam
from music21 import common
from music21.duration import Duration
from music21 import environment
from music21 import exceptions21
from music21 import expressions
from music21 import interval
from music21.pitch import Pitch
from music21 import prebase
from music21 import style
from music21 import tie
from music21 import volume
if t.TYPE_CHECKING:
from collections.abc import Iterable, Sequence
from music21.common.types import StepName, OffsetQLIn
from music21 import articulations
from music21 import chord
from music21 import instrument
from music21 import percussion
environLocal = environment.Environment('note')
noteheadTypeNames = (
'arrow down',
'arrow up',
'back slashed',
'circle dot',
'circle-x',
'circled',
'cluster',
'cross',
'diamond',
'do',
'fa',
'fa up',
'inverted triangle',
'la',
'left triangle',
'mi',
'none',
'normal',
'other',
're',
'rectangle',
'slash',
'slashed',
'so',
'square',
'ti',
'triangle',
'x',
)
'''
A tuple of the valid notehead type names accepted by a Note's
:attr:`~music21.note.NotRest.notehead` property.
'''
stemDirectionNames = (
'double',
'down',
'noStem',
'none',
'unspecified',
'up',
)
'''
A tuple of the valid stem direction names accepted by a Note's
:attr:`~music21.note.NotRest.stemDirection` property.
'''
def __dir__():
out = [n for n in globals() if not n.startswith('__') and not n.startswith('Test')]
out.remove('t')
out.remove('annotations')
out.remove('unittest')
out.remove('overload')
out.remove('environLocal')
out.remove('copy')
out.remove('_DOC_ORDER')
out.remove('Duration')
out.remove('Pitch')
out.remove('base')
out.remove('beam')
out.remove('common')
out.remove('environment')
out.remove('exceptions21')
out.remove('expressions')
out.remove('interval')
out.remove('prebase')
out.remove('style')
out.remove('tie')
out.remove('volume')
out.remove('SyllabicChoices')
return out
# -----------------------------------------------------------------------------
class LyricException(exceptions21.Music21Exception):
pass
class NoteException(exceptions21.Music21Exception):
pass
class NotRestException(exceptions21.Music21Exception):
pass
# ------------------------------------------------------------------------------
type SyllabicChoices = t.Literal[None, 'begin', 'single', 'end', 'middle', 'composite']
SYLLABIC_CHOICES: list[SyllabicChoices] = [
None, 'begin', 'single', 'end', 'middle', 'composite',
]
[docs]
class Lyric(prebase.ProtoM21Object, style.StyleMixin):
'''
An object representing a single Lyric as part of a note's .lyrics property.
The note.lyric property is a simple way of specifying a single lyric, but
Lyric objects are needed for working with multiple lyrics.
>>> l = note.Lyric(text='hello')
>>> l
<music21.note.Lyric number=1 syllabic=single text='hello'>
Music21 processes leading and following hyphens intelligently by default.
>>> l2 = note.Lyric(text='hel-')
>>> l2
<music21.note.Lyric number=1 syllabic=begin text='hel'>
If `applyRaw` is set to True then hyphens will be treated as actual text,
and the `syllabic` will be set to "single".
>>> l3 = note.Lyric(number=3, text='hel-', applyRaw=True)
>>> l3
<music21.note.Lyric number=3 syllabic=single text='hel-'>
Lyrics have four properties: text, number, identifier, syllabic (single,
begin, middle, end, or (not in musicxml) composite)
>>> l3.text
'hel-'
>>> l3.number
3
>>> l3.syllabic
'single'
Note musicXML only supports one 'identifier' attribute which is called
'number' but which can be a number or a descriptive identifier like
'part2verse1.' To preserve lyric ordering, music21 stores a number and a
descriptive identifier separately. The descriptive identifier is by default
the same as the number, but in cases where a string identifier is present,
it will be different.
Both music21 and musicxml support multiple `Lyric` objects in the same stanza,
for instance, if there is an elision on a note then multiple lyrics with
different syllabics can appear on a single note. In music21 these are supported
by setting .components into a list of `Lyric` object. For instance in
the madrigal "Il bianco e dolce cigno", the "co" and "e" of "bianco e"
are elided into a single lyric:
>>> bianco = note.Lyric()
>>> co = note.Lyric('co', syllabic='end')
>>> e = note.Lyric('e', syllabic='single')
>>> bianco.components = [co, e]
>>> bianco.isComposite
True
>>> bianco.text
'co e'
>>> bianco.syllabic
'composite'
>>> e.elisionBefore = '_'
>>> bianco.text
'co_e'
>>> [component.syllabic for component in bianco.components]
['end', 'single']
Custom elision elements for composite components will be supported later.
* New in v6.7: composite components, elisionBefore.
* Changed in v8: lyric text can be an empty string, but not None.
'''
_styleClass = style.TextStylePlacement
# CLASS VARIABLES #
__slots__ = (
'_identifier',
'_number',
'_syllabic',
'_text',
'components',
'elisionBefore',
)
# INITIALIZER #
def __init__(self,
text: str = '',
number: int = 1,
*,
applyRaw: bool = False,
syllabic: SyllabicChoices = None,
identifier: str|None = None,
**keywords):
super().__init__()
self._identifier: str|None = None
self._number: int = 1
self._text: str = ''
self._syllabic: SyllabicChoices = None
self.components: list[Lyric]|None = None
self.elisionBefore = ' '
# these are set by setTextAndSyllabic
if text:
self.setTextAndSyllabic(text, applyRaw)
# given as begin, middle, end, or single
if syllabic is not None:
self.syllabic = syllabic
self.number = number
# type ignore until https://github.com/python/mypy/issues/3004 resolved
self.identifier = identifier # type: ignore
# PRIVATE METHODS #
def _reprInternal(self):
out = ''
if self.number is not None:
out += f'number={self.number} '
if self._identifier is not None:
out += f'identifier={self.identifier!r} '
if self.syllabic is not None:
out += f'syllabic={self.syllabic} '
if self.text:
out += f'text={self.text!r} '
return out
# PUBLIC PROPERTIES #
@property
def isComposite(self) -> bool:
'''
Returns True if this Lyric has composite elements,
for instance, is multiple lyrics placed together.
'''
return bool(self.components)
@property
def text(self) -> str:
'''
Gets or sets the text of the lyric. For composite lyrics, set
the text of individual components instead of setting the text here.
>>> l = note.Lyric()
>>> l.text
''
>>> l.text = 'hi'
>>> l.text
'hi'
Setting the text of a composite lyric wipes out the components:
>>> bianco = note.Lyric()
>>> co = note.Lyric('co', syllabic='end')
>>> e = note.Lyric('e', syllabic='single')
>>> bianco.components = [co, e]
>>> bianco.isComposite
True
>>> bianco.text
'co e'
>>> bianco.text = 'co_e'
>>> bianco.isComposite
False
'''
if not self.isComposite:
return self._text
else:
if t.TYPE_CHECKING:
assert isinstance(self.components, Sequence), \
'Programming error: isComposite implies that components exists' # mypy
text_out = self.components[0].text
if text_out is None:
text_out = ''
for component in self.components[1:]:
componentText = component.text if component.text is not None else ''
text_out += component.elisionBefore + componentText
return text_out
@text.setter
def text(self, newText: str):
if self.isComposite:
self.components = None
self._text = newText
@property
def syllabic(self) -> SyllabicChoices:
'''
Returns or sets the syllabic property of a lyric.
>>> fragment = note.Lyric('frag', syllabic='begin')
>>> fragment.syllabic
'begin'
>>> fragment.rawText
'frag-'
>>> fragment.syllabic = 'end'
>>> fragment.rawText
'-frag'
Illegal values raise a LyricException
>>> fragment.syllabic = 'slide'
Traceback (most recent call last):
music21.note.LyricException: Syllabic value 'slide' is not in
note.SYLLABIC_CHOICES, namely:
[None, 'begin', 'single', 'end', 'middle', 'composite']
'''
if self.isComposite:
return 'composite'
else:
return self._syllabic
@syllabic.setter
def syllabic(self, newSyllabic: SyllabicChoices):
if newSyllabic not in SYLLABIC_CHOICES:
raise LyricException(
f'Syllabic value {newSyllabic!r} is not in '
f'note.SYLLABIC_CHOICES, namely: {SYLLABIC_CHOICES}'
)
self._syllabic = newSyllabic
@property
def identifier(self) -> str|int:
'''
By default, this is the same as self.number. However, if there is a
descriptive identifier like 'part2verse1', it is stored here and
will be different from self.number. When converting to musicXML,
this property will be stored in the lyric 'number' attribute which
can store a number or a descriptive identifier but not both.
>>> l = note.Lyric()
>>> l.number = 12
>>> l.identifier
12
>>> l.identifier = 'Rainbow'
>>> l.identifier
'Rainbow'
Default value is the same as default for number, that is, 1:
>>> note.Lyric().identifier
1
'''
if self._identifier is None:
return self._number
else:
return self._identifier
@identifier.setter
def identifier(self, value: str|None):
self._identifier = value
@property
def rawText(self) -> str:
'''
Returns the text of the syllable with '-' etc.
>>> l = note.Lyric('hel-')
>>> l.text
'hel'
>>> l.rawText
'hel-'
>>> l = note.Lyric('lo', syllabic='end')
>>> l.rawText
'-lo'
>>> l = note.Lyric('-ti-')
>>> l.rawText
'-ti-'
>>> l = note.Lyric('bye')
>>> l.rawText
'bye'
Composite lyrics take their endings from the first and last components:
>>> composite = note.Lyric()
>>> co = note.Lyric('co', syllabic='end')
>>> e = note.Lyric('e', syllabic='single')
>>> e.elisionBefore = '_'
>>> composite.components = [co, e]
>>> composite.rawText
'-co_e'
>>> e.syllabic = 'middle'
>>> composite.rawText
'-co_e-'
'''
text = self.text
if not self.isComposite:
syllabic = self.syllabic
if syllabic == 'begin':
return text + '-'
elif syllabic == 'middle':
return '-' + text + '-'
elif syllabic == 'end':
return '-' + text
else:
return text
else:
if t.TYPE_CHECKING:
assert isinstance(self.components, Sequence), \
'Programming error: isComposite should assert components exists' # for mypy
firstSyllabic = self.components[0].syllabic
lastSyllabic = self.components[-1].syllabic
if firstSyllabic in ['middle', 'end']:
text = '-' + text
if lastSyllabic in ['begin', 'middle']:
text += '-'
return text
@rawText.setter
def rawText(self, rawTextIn: str):
self.setTextAndSyllabic(rawTextIn, applyRaw=True)
@property
def number(self) -> int:
'''
This stores the number of the lyric (which determines the order
lyrics appear in the score if there are multiple lyrics). Unlike
the musicXML lyric number attribute, this value must always be a
number; lyric order is always stored in this form. Descriptive
identifiers like 'part2verse1' which can be found in the musicXML
lyric number attribute should be stored in self.identifier.
Default is 1
>>> l = note.Lyric('Hi')
>>> l.number
1
>>> l.number = 5
>>> l.number
5
>>> l.number = None
Traceback (most recent call last):
music21.note.LyricException: Number best be number
'''
return self._number
@number.setter
def number(self, value: int) -> None:
if not common.isInt(value):
raise LyricException('Number best be number')
self._number = value
# PUBLIC METHODS #
[docs]
def setTextAndSyllabic(self, rawText: str, applyRaw: bool = False) -> None:
'''
Given a setting for rawText and applyRaw,
sets the syllabic type for a lyric based on the rawText. Useful for
parsing raw text from, say, an OMR score. Or just to quickly set text
and syllabic.
>>> l = note.Lyric()
>>> l.setTextAndSyllabic('hel-')
>>> l.text
'hel'
>>> l.syllabic
'begin'
>>> l.setTextAndSyllabic('-lo')
>>> l.text
'lo'
>>> l.syllabic
'end'
>>> l.setTextAndSyllabic('the')
>>> l.text
'the'
>>> l.syllabic
'single'
If applyRaw is True then this will assume you actually want hyphens
in the text, and if syllabic is None, sets it to 'single'
>>> l = note.Lyric()
>>> l.setTextAndSyllabic('hel-', applyRaw=True)
>>> l.text
'hel-'
>>> l.syllabic
'single'
If applyRaw is True, other syllabic settings except None are retained
>>> l.syllabic = 'begin'
>>> l.setTextAndSyllabic('-lo', applyRaw=True)
>>> l.text
'-lo'
>>> l.syllabic
'begin'
This method wipes out components.
'''
# do not want to do this unless we are sure this is not a string
# possibly might alter unicode or other string-like representations
if not isinstance(rawText, str):
rawText = str(rawText)
# check for hyphens
if applyRaw is False and rawText.startswith('-') and not rawText.endswith('-'):
self.text = rawText[1:]
self.syllabic = 'end'
elif applyRaw is False and not rawText.startswith('-') and rawText.endswith('-'):
self.text = rawText[:-1]
self.syllabic = 'begin'
elif applyRaw is False and rawText.startswith('-') and rawText.endswith('-'):
self.text = rawText[1:-1]
self.syllabic = 'middle'
else: # assume single
self.text = rawText
if self.syllabic is None or not applyRaw:
self.syllabic = 'single'
# ------------------------------------------------------------------------------
[docs]
class GeneralNote(base.Music21Object):
'''
A GeneralNote object is the base class object
for the :class:`~music21.note.Note`,
:class:`~music21.note.Rest`, :class:`~music21.chord.Chord`,
and related objects.
Keywords can be passed to
a GeneralNote which are then passed to the
underlying :class:`~music21.duration.Duration`.
These keywords might be listed like
type='16th', dots=2 etc. to create a
double-dotted sixteenth note.
In almost every circumstance, you should
create note.Note() or note.Rest() or chord.Chord()
objects directly, and not use this underlying
structure.
>>> gn = note.GeneralNote(type='16th', dots=2)
>>> gn.quarterLength
0.4375
**Equality**
GeneralNote objects are equal if they pass superclass tests (e.g., their durations are equal),
and they have the same articulation and expression classes (in any order),
and their ties are equal.
'''
isNote = False
isRest = False
isChord = False
_styleClass: type[style.Style] = style.NoteStyle
equalityAttributes: tuple[str, ...] = ('tie',)
# define order for presenting names in documentation; use strings
_DOC_ORDER = ['duration', 'quarterLength']
# documentation for all attributes (not properties or methods)
_DOC_ATTR: dict[str, str] = {
'isChord': 'Boolean read-only value describing if this object is a Chord.',
'lyrics': 'A list of :class:`~music21.note.Lyric` objects.',
'expressions': '''a list of expressions (such
as :class:`~music21.expressions.Fermata`, etc.)
that are stored on this Note.''',
'articulations': '''a list of articulations such
as :class:`~music21.articulations.Staccato`, etc.) that are stored on this Note.'''
}
def __init__(self,
*,
duration: Duration|None = None,
lyric: None|str|Lyric = None,
**keywords
):
if duration is None:
# ensure music21base does not automatically create a zero duration before we can.
if not keywords or ('type' not in keywords and 'quarterLength' not in keywords):
tempDuration = Duration(1.0)
else:
tempDuration = Duration(**keywords)
if 'quarterLength' in keywords:
del keywords['quarterLength']
if 'type' in keywords:
del keywords['type']
# only apply default if components are empty
# looking at currentComponents so as not to trigger
# _updateComponents
if (tempDuration.quarterLength == 0
and not tempDuration.currentComponents()):
tempDuration.quarterLength = 1.0
else:
tempDuration = duration
# this sets the stored duration defined in Music21Object
super().__init__(duration=tempDuration, **keywords)
self.lyrics: list[Lyric] = [] # a list of lyric objects
self.expressions: list[expressions.Expression] = []
self.articulations: list[articulations.Articulation] = []
if lyric is not None:
self.addLyric(lyric)
# note: Chords handle ties differently
self._tie: tie.Tie|None = None # store a Tie object
[docs]
def __eq__(self, other):
if not super().__eq__(other):
return False
# note: tie is in equalityClasses, so already checked above.
# Articulations are a list of Articulation objects.
# Converting them to Set objects produces ordered cols that remove duplicates.
# However, we must then convert to list to match based on class ==
# not on class id().
for search in 'articulations', 'expressions':
my_art_express_classes = [type(x) for x in getattr(self, search)]
other_art_express_classes = [type(x) for x in getattr(other, search)]
if len(my_art_express_classes) != len(other_art_express_classes):
return False
if set(my_art_express_classes) != set(other_art_express_classes):
return False
return True
def __hash__(self):
return super().__hash__()
# --------------------------------------------------------------------------
@property
def tie(self) -> 'tie.Tie|None':
'''
Return and set a :class:`~music21.note.Tie` object, or None.
>>> n = note.Note()
>>> n.tie is None
True
>>> n.tie = tie.Tie('start')
'''
return self._tie
@tie.setter
def tie(self, value: 'tie.Tie|None'):
self._tie = value
@property
def lyric(self) -> str|None:
r'''
The lyric property can
be used to get and set a lyric for this
Note, Chord, or Rest. This is a simplified version of the more general
:meth:`~music21.note.GeneralNote.addLyric` method.
>>> a = note.Note('A4')
>>> a.lyrics
[]
>>> a.lyric = 'hel-'
>>> a.lyric
'hel'
>>> a.lyrics
[<music21.note.Lyric number=1 syllabic=begin text='hel'>]
Eliminate Lyrics by setting a.lyric to None
>>> a.lyric = None
>>> a.lyric
>>> a.lyrics
[]
Set multiple lyrics with \n separated text:
>>> a.lyric = '1. Hi\n2. Bye'
>>> a.lyric
'1. Hi\n2. Bye'
>>> a.lyrics
[<music21.note.Lyric number=1 syllabic=single text='1. Hi'>,
<music21.note.Lyric number=2 syllabic=single text='2. Bye'>]
You can also set a lyric with a lyric object directly:
>>> b = note.Note('B5')
>>> ly = note.Lyric('bon-')
>>> b.lyric = ly
>>> b.lyrics
[<music21.note.Lyric number=1 syllabic=begin text='bon'>]
>>> b.lyric
'bon'
* Changed in v6.7: added setting to a Lyric object. Removed undocumented
setting to False instead of setting to None.
'''
if not self.lyrics:
return None
allText = [ly.text for ly in self.lyrics]
return '\n'.join([textStr for textStr in allText if textStr is not None])
@lyric.setter
def lyric(self, value: str|Lyric|None) -> None:
self.lyrics = []
if value is None:
return
if isinstance(value, Lyric):
self.lyrics.append(value)
return
if not isinstance(value, str):
value = str(value)
values = value.split('\n')
for i, v in enumerate(values):
self.lyrics.append(Lyric(v, number=i + 1))
[docs]
def addLyric(self,
text,
lyricNumber=None,
*,
applyRaw=False,
lyricIdentifier=None) -> None:
'''
Adds a lyric, or an additional lyric, to a Note, Chord, or Rest's lyric list.
If `lyricNumber` is not None, a specific line of lyric text can be set.
The lyricIdentifier can also be set.
>>> n1 = note.Note()
>>> n1.addLyric('hello')
>>> n1.lyrics[0].text
'hello'
>>> n1.lyrics[0].number
1
An added option gives the lyric number, not the list position
>>> n1.addLyric('bye', 3)
>>> n1.lyrics[1].text
'bye'
>>> n1.lyrics[1].number
3
>>> for lyr in n1.lyrics:
... print(lyr.text)
hello
bye
Replace an existing lyric by specifying the same number:
>>> n1.addLyric('ciao', 3)
>>> n1.lyrics[1].text
'ciao'
>>> n1.lyrics[1].number
3
Giving a lyric with a hyphen at either end will set whether it
is part of a multisyllable word:
>>> n1.addLyric('good-')
>>> n1.lyrics[2].text
'good'
>>> n1.lyrics[2].syllabic
'begin'
This feature can be overridden by specifying the keyword only argument "applyRaw=True":
>>> n1.addLyric('-5', applyRaw=True)
>>> n1.lyrics[3].text
'-5'
>>> n1.lyrics[3].syllabic
'single'
'''
if not isinstance(text, str):
text = str(text)
if lyricNumber is None:
maxLyrics = len(self.lyrics) + 1
self.lyrics.append(Lyric(text, maxLyrics,
applyRaw=applyRaw, identifier=lyricIdentifier))
else:
foundLyric = False
for thisLyric in self.lyrics:
if thisLyric.number == lyricNumber:
thisLyric.text = text
foundLyric = True
break
if foundLyric is False:
self.lyrics.append(Lyric(text, lyricNumber,
applyRaw=applyRaw, identifier=lyricIdentifier))
[docs]
def insertLyric(self, text, index=0, *, applyRaw=False, identifier=None):
'''
Inserts a lyric into the Note, Chord, or Rest's lyric list in front of
the index specified (0 by default), using index + 1 as the inserted lyric's
line number. Shifts line numbers of all following lyrics in the list.
>>> n1 = note.Note()
>>> n1.addLyric('second')
>>> n1.lyrics
[<music21.note.Lyric number=1 syllabic=single text='second'>]
>>> n1.insertLyric('first', 0)
>>> n1.lyrics
[<music21.note.Lyric number=1 syllabic=single text='first'>,
<music21.note.Lyric number=2 syllabic=single text='second'>]
OMIT_FROM_DOCS
test inserting in the middle.
>>> n1.insertLyric('newSecond', 1)
>>> n1.lyrics
[<music21.note.Lyric number=1 syllabic=single text='first'>,
<music21.note.Lyric number=2 syllabic=single text='newSecond'>,
<music21.note.Lyric number=3 syllabic=single text='second'>]
Test number as lyric:
>>> n1.insertLyric(0, 3)
>>> n1.lyrics
[<music21.note.Lyric number=1 syllabic=single text='first'>,
<music21.note.Lyric number=2 syllabic=single text='newSecond'>,
<music21.note.Lyric number=3 syllabic=single text='second'>,
<music21.note.Lyric number=4 syllabic=single text='0'>]
'''
if not isinstance(text, str):
text = str(text)
for lyric in self.lyrics[index:]:
lyric.number += 1
self.lyrics.insert(index, Lyric(text, (index + 1),
applyRaw=applyRaw, identifier=identifier))
# --------------------------------------------------------------------------
# properties common to Notes, Rests, etc.
@property
def fullName(self) -> str:
return self.classes[0] # override in subclasses
@property
def pitches(self) -> tuple[Pitch, ...]:
'''
Returns an empty tuple. (Useful for iterating over GeneralNotes since they
include Notes and Chords.)
'''
return ()
@pitches.setter
def pitches(self, _value: Iterable[Pitch]) -> None:
pass
# --------------------------------------------------------------------------
[docs]
def augmentOrDiminish(self, scalar, *, inPlace=False):
'''
Given a scalar greater than zero, return a Note with a scaled Duration.
If `inPlace` is True, this is done in-place and the method returns None.
If `inPlace` is False [default], this returns a modified deepcopy.
* Changed in v5: inPlace is now False.
>>> n = note.Note('g#')
>>> n.quarterLength = 3
>>> n.augmentOrDiminish(2, inPlace=True)
>>> n.quarterLength
6.0
>>> c = chord.Chord(['g#', 'a#', 'd'])
>>> c.quarterLength = 2
>>> c.augmentOrDiminish(0.25, inPlace=True)
>>> c.quarterLength
0.5
>>> n = note.Note('g#')
>>> n.augmentOrDiminish(-1)
Traceback (most recent call last):
music21.note.NoteException: scalar must be greater than zero
>>> n = note.Note()
>>> n.quarterLength = 3
>>> n2 = n.augmentOrDiminish(1/3, inPlace=False)
>>> n2.quarterLength
1.0
>>> n.quarterLength
3.0
'''
if not scalar > 0:
raise NoteException('scalar must be greater than zero')
if inPlace:
post = self
else: # slight speedup could happen by setting duration to Zero before copying.
post = copy.deepcopy(self)
# this is never True.
post.duration = post.duration.augmentOrDiminish(scalar)
if not inPlace:
return post
else:
return None
# --------------------------------------------------------------------------
[docs]
def getGrace(self, *, appoggiatura=False, inPlace=False):
'''
Return a grace version of this GeneralNote
>>> n = note.Note('G4', quarterLength=2)
>>> n.duration.quarterLength
2.0
>>> n.duration.isGrace
False
>>> n.duration
<music21.duration.Duration 2.0>
>>> n.duration.type
'half'
>>> n.duration.components
(DurationTuple(type='half', dots=0, quarterLength=2.0),)
>>> ng = n.getGrace()
>>> ng.duration.quarterLength
0.0
>>> ng.duration.isGrace
True
>>> ng.duration
<music21.duration.GraceDuration unlinked type:half quarterLength:0.0>
>>> ng.duration.type
'half'
>>> ng.duration.components
(DurationTuple(type='half', dots=0, quarterLength=0.0),)
Appoggiaturas are still a work in progress.
* Changed in v6: corrected spelling of `appoggiatura` keyword.
>>> ng2 = n.getGrace(appoggiatura=True)
>>> ng2.duration
<music21.duration.AppoggiaturaDuration unlinked type:half quarterLength:0.0>
>>> ng2.duration.slash
False
Set inPlace to True to change the duration element on the Note. This can have
negative consequences if the Note is in a stream.
>>> r = note.Rest(quarterLength=0.5)
>>> r.getGrace(inPlace=True)
>>> r.duration
<music21.duration.GraceDuration unlinked type:eighth quarterLength:0.0>
'''
if inPlace is False:
e = copy.deepcopy(self)
else:
e = self
e.duration = e.duration.getGraceDuration(appoggiatura=appoggiatura)
if inPlace is False:
return e
# ------------------------------------------------------------------------------
[docs]
class NotRest(GeneralNote):
'''
Parent class for Note-like objects that are not rests; that is to say
they have a stem, can be tied, and volume is important.
Basically, that's a :class:`Note` or :class:`~music21.chord.Chord`
(or their subclasses such as :class:`~music21.harmony.ChordSymbol`), or
:class:`Unpitched` object.
NotRest elements are generally not created on their own. It is a class
that exists to store common functionality used by Note, Unpitched, and Chord objects.
>>> nr = note.NotRest(storedInstrument=instrument.Ocarina())
>>> nr.stemDirection = 'up'
* Changed in v9: beams is keyword only. Added storedInstrument keyword.
'''
# unspecified means that there may be a stem, but its orientation
# has not been declared.
_DOC_ATTR: dict[str, str] = {
'beams': '''
A :class:`~music21.beam.Beams` object that contains
information about the beaming of this note.''',
}
# Should volume be here too? and _chordAttached?
equalityAttributes: tuple[str, ...] = (
'notehead', 'noteheadFill', 'noteheadParenthesis', 'beams'
)
def __init__(self,
*,
beams: beam.Beams|None = None,
storedInstrument: instrument.Instrument|None = None,
**keywords):
super().__init__(**keywords)
self._notehead: str = 'normal'
self._noteheadFill: bool|None = None
self._noteheadParenthesis: bool = False
self._stemDirection: str = 'unspecified'
self._volume: volume.Volume|None = None # created on demand
if beams is not None:
self.beams = beams
else:
self.beams = beam.Beams()
self._storedInstrument: instrument.Instrument|None = storedInstrument
self._chordAttached: chord.ChordBase|None = None
# ==============================================================================================
# Special functions
# ==============================================================================================
def _deepcopySubclassable(self,
memo: dict[int, t.Any]|None = None,
*,
ignoreAttributes: set[str]|None = None) -> t.Self:
new = super()._deepcopySubclassable(memo, ignoreAttributes={'_chordAttached'})
if t.TYPE_CHECKING:
new = t.cast(t.Self, new)
# let the chord restore _chordAttached
# after copying, if a Volume exists, it is linked to the old object
# look at _volume so as not to create object if not already there
if self.hasVolumeInformation():
new.volume.client = new # update with new instance
return new
def __deepcopy__(self, memo=None):
'''
As NotRest objects have a Volume, objects, and Volume objects
store refs to the client object, need to specialize deepcopy handling
>>> import copy
>>> n = note.NotRest()
>>> n.volume = volume.Volume(velocity=50)
>>> m = copy.deepcopy(n)
>>> m.volume.client is m
True
>>> n.volume.client is n
True
'''
# environLocal.printDebug(['calling NotRest.__deepcopy__', self])
return self._deepcopySubclassable(memo=memo)
@property
def stemDirection(self) -> str:
r'''
Get or set the stem direction of this NotRest object.
Valid stem direction names are found in note.stemDirectionNames (see below).
>>> note.stemDirectionNames
('double', 'down', 'noStem', 'none', 'unspecified', 'up')
>>> n = note.Note()
By default, a Note's stemDirection is 'unspecified'
meaning that it is unknown:
>>> n.stemDirection
'unspecified'
>>> n.stemDirection = 'noStem'
>>> n.stemDirection
'noStem'
The alias 'none' (the string) is the same as 'noStem'
>>> n.stemDirection = 'none'
>>> n.stemDirection
'noStem'
>>> n.stemDirection = 'junk'
Traceback (most recent call last):
music21.note.NotRestException: not a valid stem direction name: junk
Stem direction can be set explicitly to None to remove
any prior stem information, same as 'unspecified':
>>> n.stemDirection = None
>>> n.stemDirection
'unspecified'
'''
return self._stemDirection
@stemDirection.setter
def stemDirection(self, direction: None|str):
if direction is None:
direction = 'unspecified' # allow setting to None meaning
elif direction == 'none':
direction = 'noStem' # allow setting to none or None
elif direction not in stemDirectionNames:
raise NotRestException(f'not a valid stem direction name: {direction}')
self._stemDirection = direction
@property
def notehead(self) -> str:
'''
Get or set the notehead type of this NotRest object.
Valid notehead type names are found in note.noteheadTypeNames (see below):
>>> note.noteheadTypeNames
('arrow down', 'arrow up', 'back slashed', 'circle dot', 'circle-x', 'circled', 'cluster',
'cross', 'diamond', 'do', 'fa', 'fa up', 'inverted triangle', 'la', 'left triangle',
'mi', 'none', 'normal', 'other', 're', 'rectangle', 'slash', 'slashed', 'so',
'square', 'ti', 'triangle', 'x')
>>> n = note.Note()
>>> n.notehead = 'diamond'
>>> n.notehead
'diamond'
>>> n.notehead = 'junk'
Traceback (most recent call last):
music21.note.NotRestException: not a valid notehead type name: 'junk'
'''
return self._notehead
@notehead.setter
def notehead(self, value):
if value in ('none', None, ''):
value = None # allow setting to none or None
elif value not in noteheadTypeNames:
raise NotRestException(f'not a valid notehead type name: {value!r}')
self._notehead = value
@property
def noteheadFill(self) -> bool|None:
'''
Get or set the note head fill status of this NotRest. Valid note head fill values are
True, False, or None (meaning default). "yes" and "no" are converted to True
and False.
>>> n = note.Note()
>>> n.noteheadFill = 'no'
>>> n.noteheadFill
False
>>> n.noteheadFill = 'filled'
>>> n.noteheadFill
True
>>> n.noteheadFill = 'jelly'
Traceback (most recent call last):
music21.note.NotRestException: not a valid notehead fill value: 'jelly'
'''
return self._noteheadFill
@noteheadFill.setter
def noteheadFill(self, value: bool|None|str):
boolValue: bool|None
if value in ('none', None, 'default'):
boolValue = None # allow setting to none or None
elif value in (True, 'filled', 'yes'):
boolValue = True
elif value in (False, 'notfilled', 'no'):
boolValue = False
else:
raise NotRestException(f'not a valid notehead fill value: {value!r}')
self._noteheadFill = boolValue
@property
def noteheadParenthesis(self) -> bool:
'''
Get or set the note head parentheses for this Note/Unpitched/Chord object.
>>> n = note.Note()
>>> n.noteheadParenthesis
False
>>> n.noteheadParenthesis = True
>>> n.noteheadParenthesis
True
'yes' or 1 equate to True; 'no' or 0 to False
>>> n.noteheadParenthesis = 'no'
>>> n.noteheadParenthesis
False
Anything else raises an exception:
>>> n.noteheadParenthesis = 'blah'
Traceback (most recent call last):
music21.note.NotRestException: notehead parentheses must be True or False, not 'blah'
'''
return self._noteheadParenthesis
@noteheadParenthesis.setter
def noteheadParenthesis(self, value: bool|str|int):
boolValue: bool
if value in (True, 'yes', 1):
boolValue = True
elif value in (False, 'no', 0):
boolValue = False
else:
raise NotRestException(f'notehead parentheses must be True or False, not {value!r}')
self._noteheadParenthesis = boolValue
# --------------------------------------------------------------------------
def _getVolume(self,
forceClient: NotRest|None = None
) -> volume.Volume:
# DO NOT CHANGE TO @property because of optional attributes
# lazy volume creation. property is set below.
if self._volume is None:
if forceClient is None:
# when creating the volume object, set the client as self
self._volume = volume.Volume(client=self)
else:
self._volume = volume.Volume(client=forceClient)
volume_out = self._volume
if t.TYPE_CHECKING:
assert volume_out is not None
return volume_out
def _setVolume(self, value: None|volume.Volume|int|float, setClient=True):
# DO NOT CHANGE TO @property because of optional attributes
# setClient is only False when Chords are bundling Notes.
if value is None:
self._volume = None
elif isinstance(value, volume.Volume):
if setClient:
if value.client is not None:
value = copy.deepcopy(value)
value.client = self # set to self
self._volume = value
elif common.isNum(value) and setClient:
# if we can define the client, we can set from numbers
# call local getVolume will set client appropriately
vol = self._getVolume()
if value < 1: # assume a scalar
vol.velocityScalar = float(value)
else: # assume velocity
vol.velocity = int(value)
else:
raise TypeError(f'this must be a Volume object, not {value}')
@property
def volume(self) -> 'volume.Volume':
'''
Get and set the :class:`~music21.volume.Volume` object of this object.
Volume objects are created on demand.
>>> n1 = note.Note()
>>> n1.volume.velocity = 120
>>> n2 = note.Note()
>>> n2.volume = 80 # can directly set a velocity value
>>> s = stream.Stream()
>>> s.append([n1, n2])
>>> [n.volume.velocity for n in s.notes]
[120, 80]
'''
return self._getVolume()
@volume.setter
def volume(self, value: 'None|volume.Volume|int|float'):
self._setVolume(value)
@property
def storedInstrument(self) -> instrument.Instrument|None:
'''
Get or set the :class:`~music21.instrument.Instrument` that
should be used to play this note, overriding whatever
Instrument object may be active in the Stream. (See
:meth:`getInstrument` for a means of retrieving `storedInstrument`
if available before falling back to a context search to find
the active instrument.)
>>> snare = note.Unpitched()
>>> snare.storedInstrument = instrument.SnareDrum()
>>> snare.storedInstrument
<music21.instrument.SnareDrum 'Snare Drum'>
>>> snare
<music21.note.Unpitched 'Snare Drum'>
'''
return self._storedInstrument
@storedInstrument.setter
def storedInstrument(self, newValue: instrument.Instrument|None):
if (newValue is not None
and (not hasattr(newValue, 'classSet')
or 'music21.instrument.Instrument' not in newValue.classSet)):
raise TypeError(f'Expected Instrument; got {type(newValue)}')
self._storedInstrument = newValue
@overload
def getInstrument(self,
*,
returnDefault: t.Literal[True] = True
) -> instrument.Instrument:
...
@overload
def getInstrument(self,
*,
returnDefault: t.Literal[False]
) -> instrument.Instrument|None:
...
[docs]
def getInstrument(self,
*,
returnDefault: bool = True
) -> instrument.Instrument|None:
'''
Retrieves the `.storedInstrument` on this `NotRest` instance, if any.
If one is not found, executes a context search (without following
derivations) to find the closest (i.e., active) instrument in the
stream hierarchy.
Returns a default instrument if no instrument is found in the context
and `returnDefault` is True (default).
>>> n = note.Note()
>>> m = stream.Measure([n])
>>> n.getInstrument(returnDefault=False) is None
True
>>> dulcimer = instrument.Dulcimer()
>>> m.insert(0, dulcimer)
>>> n.getInstrument() is dulcimer
True
Overridden `.storedInstrument` is privileged:
>>> picc = instrument.Piccolo()
>>> n.storedInstrument = picc
>>> n.getInstrument() is picc
True
Instruments in containing streams ARE found:
>>> n.storedInstrument = None
>>> m.remove(dulcimer)
>>> p = stream.Part([m])
>>> p.insert(0, dulcimer)
>>> n.getInstrument() is dulcimer
True
But not if the instrument is only found in a derived stream:
>>> derived = p.stripTies()
>>> p.remove(dulcimer)
>>> derived.getInstruments().first()
<music21.instrument.Dulcimer 'Dulcimer'>
>>> n.getInstrument(returnDefault=False) is None
True
Electing to return a default generic `Instrument`:
>>> n.getInstrument(returnDefault=True)
<music21.instrument.Instrument ''>
'''
from music21 import instrument
if self.storedInstrument is not None:
return self.storedInstrument
instrument_or_none = self.getContextByClass(
instrument.Instrument, followDerivation=False)
if returnDefault and instrument_or_none is None:
return instrument.Instrument()
elif instrument_or_none is None:
return None
return t.cast(instrument.Instrument, instrument_or_none)
# ------------------------------------------------------------------------------
[docs]
class Note(NotRest):
'''
One of the most important music21 classes, a Note
stores a single note (that is, not a rest or an unpitched element)
that can be represented by one or more notational units -- so
for instance a C quarter-note and a D# eighth-tied-to-32nd are both
a single Note object.
A Note knows both its total duration and how to express itself as a set of
tied notes of different lengths. For instance, a note of 2.5 quarters in
length could be half tied to eighth or dotted quarter tied to quarter.
The first argument to the Note is the pitch name (with or without
octave, see the introduction to :class:`music21.pitch.Pitch`).
Further arguments can be specified as keywords (such as type, dots, etc.)
and are passed to the underlying :class:`music21.duration.Duration` element.
>>> n = note.Note()
>>> n
<music21.note.Note C>
>>> n.pitch
<music21.pitch.Pitch C4>
>>> n = note.Note('B-')
>>> n.name
'B-'
>>> n.octave is None
True
>>> n.pitch.implicitOctave
4
>>> n = note.Note(name='D#')
>>> n.name
'D#'
>>> n = note.Note(nameWithOctave='D#5')
>>> n.nameWithOctave
'D#5'
Other ways of instantiating a Pitch object, such as by MIDI number or pitch class
are also possible:
>>> note.Note(64).nameWithOctave
'E4'
All keyword args that are valid for Duration or Pitch objects
are valid (as well as those for superclasses, NotRest, GeneralNote,
Music21Object):
>>> n = note.Note(step='C', accidental='sharp', octave=2, id='csharp', type='eighth', dots=2)
>>> n.nameWithOctave
'C#2'
>>> n.duration
<music21.duration.Duration 0.875>
**Equality and ordering**
Two notes are equal if they pass all the equality tests for NotRest and their
pitches are equal.
Attributes that might change based on the wider context
of a note (such as offset) are not compared. This test does not look at lyrics in
establishing equality. (It may in the future.)
>>> note.Note('C4') == note.Note('C4')
True
Enharmonics are not equal:
>>> note.Note('D#4') == note.Note('E-4')
False
>>> note.Note('C4', type='half') == note.Note('C4', type='quarter')
False
Notes, like pitches, also have an ordering based on their pitches.
>>> highE = note.Note('E5')
>>> lowF = note.Note('F2')
>>> otherHighE = note.Note('E5')
>>> highE > lowF
True
>>> highE < lowF
False
>>> highE >= otherHighE
True
>>> highE <= otherHighE
True
Notice you cannot compare Notes w/ ints or anything that does not have a
`.pitch` attribute.
>>> highE < 50
Traceback (most recent call last):
TypeError: '<' not supported between instances of 'Note' and 'int'
Note also that two objects can be >= and <= without being equal, because
only pitch-height is being compared in <, <=, >, >= but duration and other
elements are compared in equality.
>>> otherHighE.duration.type = 'whole'
Now otherHighE is != highE
>>> highE == otherHighE
False
But it is both >= and <= it:
>>> highE >= otherHighE
True
>>> highE <= otherHighE
True
(The pigeonhole principle police have a bounty out on my head for this.)
'''
isNote = True
equalityAttributes: tuple[str, ...] = ('pitch',)
# Defines the order of presenting names in the documentation; use strings
_DOC_ORDER = ['duration', 'quarterLength', 'nameWithOctave']
# documentation for all attributes (not properties or methods)
_DOC_ATTR: dict[str, str] = {
'isNote': 'Boolean read-only value describing if this Note is a Note (True).',
'isRest': 'Boolean read-only value describing if this Note is a Rest (False).',
'pitch': '''A :class:`~music21.pitch.Pitch` object containing all the
information about the note's pitch. Many `.pitch` properties and
methods are also made `Note` properties also''',
}
# Accepts an argument for pitch
def __init__(self,
pitch: str|int|Pitch|None = None,
*,
name: str|None = None,
nameWithOctave: str|None = None,
**keywords):
super().__init__(**keywords)
self._chordAttached: chord.Chord|None
if pitch is not None:
if isinstance(pitch, Pitch):
self.pitch = pitch
else: # assume first argument is pitch
self.pitch = Pitch(pitch, **keywords)
else: # supply a default pitch
if nameWithOctave is not None:
name = nameWithOctave
elif not name:
name = 'C4'
self.pitch = Pitch(name, **keywords)
# noinspection PyProtectedMember
self.pitch._client = self
# --------------------------------------------------------------------------
# operators, representations, and transformations
def _reprInternal(self):
return self.name
def __lt__(self, other):
try:
return self.pitch < other.pitch
except AttributeError:
return NotImplemented
# do not factor out into @total_ordering because of the difference between __eq__ and
# the equal part of __le__ and __ge__
def __gt__(self, other):
try:
return self.pitch > other.pitch
except AttributeError:
return NotImplemented
def __le__(self, other):
try:
return self.pitch <= other.pitch
except AttributeError:
return NotImplemented
def __ge__(self, other):
try:
return self.pitch >= other.pitch
except AttributeError:
return NotImplemented
def __deepcopy__(self, memo=None) -> t.Self:
'''
After doing a deepcopy of the pitch, be sure to set the client
'''
new = self._deepcopySubclassable(memo)
# noinspection PyProtectedMember
new.pitch._client = new
return new
# --------------------------------------------------------------------------
# property access
@property
def name(self) -> str:
'''
Return or set the pitch name from the :class:`~music21.pitch.Pitch` object.
See `Pitch`'s attribute :attr:`~music21.pitch.Pitch.name`.
'''
return self.pitch.name
@name.setter
def name(self, value: str):
self.pitch.name = value
@property
def nameWithOctave(self) -> str:
'''
Return or set the pitch name with octave from the :class:`~music21.pitch.Pitch` object.
See `Pitch`'s attribute :attr:`~music21.pitch.Pitch.nameWithOctave`.
'''
return self.pitch.nameWithOctave
@nameWithOctave.setter
def nameWithOctave(self, value: str):
self.pitch.nameWithOctave = value
@property
def step(self) -> StepName:
'''
Return or set the pitch step from the :class:`~music21.pitch.Pitch` object.
See :attr:`~music21.pitch.Pitch.step`.
'''
return self.pitch.step
@step.setter
def step(self, value: StepName):
self.pitch.step = value
@property
def octave(self) -> int|None:
'''
Return or set the octave value from the :class:`~music21.pitch.Pitch` object.
See :attr:`~music21.pitch.Pitch.octave`.
'''
return self.pitch.octave
@octave.setter
def octave(self, value: int|None):
self.pitch.octave = value
@property
def pitches(self) -> tuple[Pitch, ...]:
'''
Return the single :class:`~music21.pitch.Pitch` object in a tuple.
This property is designed to provide an interface analogous to
that found on :class:`~music21.chord.Chord` so that `[c.pitches for c in s.notes]`
provides a consistent interface for all objects.
>>> n = note.Note('g#')
>>> n.nameWithOctave
'G#'
>>> n.pitches
(<music21.pitch.Pitch G#>,)
Since this is a Note, not a chord, from the list or tuple,
only the first one will be used:
>>> n.pitches = [pitch.Pitch('c2'), pitch.Pitch('g2')]
>>> n.nameWithOctave
'C2'
>>> n.pitches
(<music21.pitch.Pitch C2>,)
The value for setting must be a list or tuple:
>>> n.pitches = pitch.Pitch('C4')
Traceback (most recent call last):
music21.note.NoteException: cannot set pitches with provided object: C4
For setting a single one, use `n.pitch` instead.
Don't use strings, or you will get a string back!
>>> n.pitches = ('C4', 'D4')
>>> n.pitch
'C4'
>>> n.pitch.diatonicNoteNum
Traceback (most recent call last):
AttributeError: 'str' object has no attribute 'diatonicNoteNum'
'''
return (self.pitch,)
@pitches.setter
def pitches(self, value: Iterable[Pitch]) -> None:
if common.isListLike(value) and value:
self.pitch = value[0]
else:
raise NoteException(f'cannot set pitches with provided object: {value}')
[docs]
def transpose(self, value, *, inPlace=False):
'''
Transpose the Note by the user-provided
value. If the value is an integer, the transposition is treated in half steps.
If the value is a string, any Interval string specification can be provided.
>>> a = note.Note('g4')
>>> b = a.transpose('m3')
>>> b
<music21.note.Note B->
>>> aInterval = interval.Interval(-6)
>>> b = a.transpose(aInterval)
>>> b
<music21.note.Note C#>
>>> c = b.transpose(interval.GenericInterval(2))
>>> c
<music21.note.Note D#>
>>> a.transpose(aInterval, inPlace=True)
>>> a
<music21.note.Note C#>
If the transposition value is an integer, take the KeySignature or Key context
into account
>>> s = stream.Stream()
>>> s.append(key.Key('D'))
>>> s.append(note.Note('F'))
>>> s.append(key.Key('b-', 'minor'))
>>> s.append(note.Note('F'))
>>> s.show('text')
{0.0} <music21.key.Key of D major>
{0.0} <music21.note.Note F>
{1.0} <music21.key.Key of b- minor>
{1.0} <music21.note.Note F>
>>> for n in s.notes:
... n.transpose(1, inPlace=True)
>>> s.show('text')
{0.0} <music21.key.Key of D major>
{0.0} <music21.note.Note F#>
{1.0} <music21.key.Key of b- minor>
{1.0} <music21.note.Note G->
'''
from music21 import key
if isinstance(value, interval.IntervalBase):
intervalObj = value
else: # try to process
intervalObj = interval.Interval(value)
if not inPlace:
post = copy.deepcopy(self)
else:
post = self
# use inPlace, b/c if we are inPlace, we operate on self;
# if we are not inPlace, post is a copy
post.pitch.transpose(intervalObj, inPlace=True)
if (post.pitch.accidental is not None
and isinstance(value, (int, interval.ChromaticInterval))):
ksContext = self.getContextByClass(key.KeySignature)
if ksContext is not None:
for alteredPitch in ksContext.alteredPitches:
if (post.pitch.pitchClass == alteredPitch.pitchClass
and post.pitch.accidental.alter != alteredPitch.accidental.alter):
post.pitch.getEnharmonic(inPlace=True)
if not inPlace:
post.derivation.method = 'transpose'
return post
else:
return None
@property
def fullName(self) -> str:
'''
Return the most complete representation of this Note,
providing duration and pitch information.
>>> n = note.Note('A-', quarterLength=1.5)
>>> n.fullName
'A-flat Dotted Quarter Note'
>>> n = note.Note('E~3', quarterLength=2)
>>> n.fullName
'E-half-sharp in octave 3 Half Note'
>>> n = note.Note('D', quarterLength=0.25)
>>> n.pitch.microtone = 25
>>> n.fullName
'D (+25c) 16th Note'
'''
msg = []
msg.append(self.pitch.fullName + ' ')
msg.append(self.duration.fullName)
msg.append(' Note')
return ''.join(msg)
[docs]
def pitchChanged(self):
'''
Called by the underlying pitch if something changed there.
'''
self._cache = {}
if self._chordAttached is not None:
self._chordAttached.clearCache()
# ------------------------------------------------------------------------------
# convenience classes
# ------------------------------------------------------------------------------
[docs]
class Unpitched(NotRest):
'''
A General class of unpitched objects which appear at different places
on the staff. Examples: percussion notation.
>>> unp = note.Unpitched()
Unpitched elements have :attr:`displayStep` and :attr:`displayOctave`,
which shows where they should be displayed as if the staff were a
5-line staff in treble clef, but they do not have pitch
objects:
>>> unp.displayStep
'B'
>>> unp.displayOctave
4
>>> unp.displayStep = 'G'
>>> unp.pitch
Traceback (most recent call last):
AttributeError: 'Unpitched' object has no attribute 'pitch...
Unpitched elements generally have an instrument object associated with them:
>>> unp.storedInstrument = instrument.Woodblock()
>>> unp
<music21.note.Unpitched 'Woodblock'>
Two unpitched objects compare the same if their instrument and displayStep and
displayOctave are equal (and satisfy all the equality requirements of
their base classes):
>>> unp2 = note.Unpitched()
>>> unp == unp2
False
>>> unp2.displayStep = 'G'
>>> unp2.storedInstrument = instrument.Woodblock()
>>> unp == unp2
True
>>> unp2.storedInstrument = instrument.Triangle()
>>> unp == unp2
False
'''
# TODO: when Python 3.12 is minimum version. Change AttributeError to read:
# AttributeError: 'Unpitched' object has no attribute 'pitch'. Did you mean: 'pitches'?
equalityAttributes = ('displayStep', 'displayOctave', 'storedInstrument')
def __init__(
self,
displayName: str|None = None,
**keywords
):
super().__init__(**keywords)
self._chordAttached: percussion.PercussionChord|None = None
self.displayStep: StepName = 'B'
self.displayOctave: int = 4
if displayName:
display_pitch = Pitch(displayName)
self.displayStep = display_pitch.step
self.displayOctave = display_pitch.implicitOctave
def _reprInternal(self):
if not self.storedInstrument:
return ''
else:
return repr(self.storedInstrument.instrumentName)
[docs]
def displayPitch(self) -> Pitch:
'''
Returns a pitch object that is the same as the displayStep and displayOctave.
It will never have an accidental.
>>> unp = note.Unpitched()
>>> unp.displayStep = 'E'
>>> unp.displayOctave = 4
>>> unp.displayPitch()
<music21.pitch.Pitch E4>
'''
return Pitch(step=self.displayStep, octave=self.displayOctave)
@property
def displayName(self) -> str:
'''
Returns the `nameWithOctave` of the :meth:`displayPitch`.
>>> unp = note.Unpitched('B2')
>>> unp.displayName
'B2'
'''
display_pitch = self.displayPitch()
return display_pitch.nameWithOctave
# ------------------------------------------------------------------------------
[docs]
class Rest(GeneralNote):
'''
Rests are represented in music21 as GeneralNote objects that do not have
a pitch object attached to them. By default, they have length 1.0 (Quarter Rest)
Calling :attr:`~music21.stream.Stream.notes` on a Stream does not get rests.
However, the property :attr:`~music21.stream.Stream.notesAndRests` of Streams
gets rests as well.
>>> r = note.Rest()
>>> isinstance(r, note.Note)
False
>>> r.duration.quarterLength = 2.0
>>> r.duration.type
'half'
All Rests have the name property 'rest':
>>> r.name
'rest'
And their .pitches is an empty tuple
>>> r.pitches
()
All arguments to Duration are valid in constructing:
>>> r2 = note.Rest(type='whole')
>>> r2.duration.quarterLength
4.0
Or they can just be specified in without a type, and they'll be evaluated automatically
>>> r3, r4 = note.Rest('half'), note.Rest(2.0)
>>> r3 == r4
True
>>> r3.duration.quarterLength
2.0
Two rests are considered equal if their durations are equal.
>>> r1 = note.Rest('quarter')
>>> r2 = note.Rest('quarter')
>>> r1 == r2
True
>>> r1 != r2
False
>>> r2.duration.quarterLength = 4/3
>>> r1 == r2
False
A rest is never equal to a note.
>>> r1 == note.Note()
False
Currently, there are these convenience features, but they are going away
(They were originally added because isinstance was slow. It is now very fast)
>>> r.isRest
True
>>> r.isNote
False
'''
isRest = True
name = 'rest'
_DOC_ATTR: dict[str, str] = {
'isNote': 'Boolean read-only value describing if this Rest is a Note (False).',
'isRest': 'Boolean read-only value describing if this Rest is a Rest (True, obviously).',
'name': '''returns "rest" always. It is here so that you can get
`x.name` on all `.notesAndRests` objects''',
'stepShift': 'number of lines/spaces to shift the note upwards or downwards for display.',
'fullMeasure': '''does this rest last a full measure or if it does, should it display
itself as whole rest (or breve rest) and centered.
Options are "auto" (default), False, True, and "always"
"auto" is the default, where if the rest value happens to match the current
time signature context (and there is no pickup or other padding),
then display it as a whole rest, centered, etc. otherwise will display normally.
False means do not display the rest as full measure whole rest,
no matter what. This setting is often used by composers in very small time
signatures such as 1/8, where a whole rest can look incongruous.
True keeps the set duration, but will always display as a full measure rest
even if it's not the length of the measure
(generally a whole note unless the time signature is very long).
"always" means that on export, the duration will (EVENTUALLY, not yet!)
update automatically to match the time signature context and always display
as a whole rest. "always" does not work yet -- functions as True.
See examples in :meth:`music21.musicxml.m21ToXml.MeasureExporter.restToXml`
''',
}
def __init__(self,
length: str|OffsetQLIn|None = None,
*,
stepShift: int = 0,
fullMeasure: t.Literal[True, False, 'auto', 'always'] = 'auto',
**keywords):
if length is not None:
if isinstance(length, str) and 'type' not in keywords:
keywords['type'] = length
elif 'quarterLength' not in keywords:
keywords['quarterLength'] = length
super().__init__(**keywords)
self.stepShift = stepShift # display line
# TODO: fullMeasure=='always' does not work properly
self.fullMeasure = fullMeasure # see docs; True, False, 'always',
def _reprInternal(self):
duration_name = self.duration.fullName.lower()
if len(duration_name) < 15: # dotted quarter = 14
return duration_name.replace(' ', '-')
else:
ql = self.duration.quarterLength
if ql == int(ql):
ql = int(ql)
ql_string = str(ql)
return f'{ql_string}ql'
@property
def fullName(self) -> str:
'''
Return the most complete representation of this Rest,
providing duration information.
>>> r = note.Rest(quarterLength=1.5)
>>> r.fullName
'Dotted Quarter Rest'
>>> note.Rest(type='whole').fullName
'Whole Rest'
'''
return self.duration.fullName + ' Rest'
# ------------------------------------------------------------------------------
# test methods and classes
class TestExternal(unittest.TestCase):
'''
These are tests that open windows and rely on external software
'''
show = True
def testSingle(self):
'''
Need to test direct note creation w/o stream
'''
from music21 import note
a = note.Note('D-3')
a.quarterLength = 2.25
if self.show:
a.show()
def testBasic(self):
from music21 import note
from music21 import stream
a = stream.Stream()
for pitchName, qLen in [('d-3', 2.5), ('c#6', 3.25), ('a--5', 0.5),
('f', 1.75), ('g3', 1.5), ('d##4', 1.25),
('d-3', 2.5), ('c#6', 3.25), ('a--5', 0.5),
('f#2', 1.75), ('g-3', (4 / 3)), ('d#6', (2 / 3))
]:
b = note.Note()
b.quarterLength = qLen
b.name = pitchName
# Pylint going crazy here
b.style.color = '#FF00FF' # pylint: disable=attribute-defined-outside-init
a.append(b)
if self.show:
a.show()
# ------------------------------------------------------------------------------
class Test(unittest.TestCase):
def testCopyAndDeepcopy(self):
from music21.test.commonTest import testCopyAll
testCopyAll(self, globals())
# ------------------------------------------------------------------------------
# define presented order in documentation
_DOC_ORDER = [Note, Rest, Unpitched, NotRest, GeneralNote, Lyric]
if __name__ == '__main__':
# sys.arg test options will be used in mainTest()
import music21
music21.mainTest(Test)