Source code for music21.note

# ------------------------------------------------------------------------------
# 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 # --------------------------------------------------------------------------
[docs] def hasVolumeInformation(self) -> bool: ''' Returns bool whether volume was set -- saving some time for advanced users (such as MusicXML exporters) that only want to look at the volume if it is already there. >>> n = note.Note() >>> n.hasVolumeInformation() False >>> n.volume <music21.volume.Volume realized=0.71> >>> n.hasVolumeInformation() True ''' if self._volume is None: return False else: return True
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)