Source code for music21.bar

# ------------------------------------------------------------------------------
# Name:         bar.py
# Purpose:      music21 classes for representing bars, repeats, and related
#
# Authors:      Michael Scott Asato Cuthbert
#               Christopher Ariza
#
# Copyright:    Copyright © 2009-2012, 2020 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Object models of barlines, including repeat barlines.
'''
from __future__ import annotations

import unittest

from music21 import base
from music21 import environment
from music21 import exceptions21
from music21 import expressions
from music21 import repeat

environLocal = environment.Environment('bar')

# ------------------------------------------------------------------------------

class BarException(exceptions21.Music21Exception):
    pass


# store alternative names for types; use this dictionary for translation
# reference
barTypeList = [
    'regular', 'dotted', 'dashed', 'heavy', 'double', 'final',
    'heavy-light', 'heavy-heavy', 'tick', 'short', 'none',
]

# former are MusicXML names we allow
barTypeDict = {
    'light-light': 'double',
    'light-heavy': 'final'
}
reverseBarTypeDict = {
    'double': 'light-light',
    'final': 'light-heavy',
}

strongBarlineTypes = {'heavy', 'double', 'final', 'heavy-light', 'heavy-heavy'}  # set


[docs] def typeToMusicXMLBarStyle(value): ''' Convert a music21 barline name into the musicxml name -- essentially just changes the names of 'double' and 'final' to 'light-light' and 'light-heavy'. Does not do error checking to make sure it's a valid name, since setting the style on a Barline object already does that. >>> bar.typeToMusicXMLBarStyle('final') 'light-heavy' >>> bar.typeToMusicXMLBarStyle('regular') 'regular' ''' if value.lower() in reverseBarTypeDict: return reverseBarTypeDict[value.lower()] else: return value
[docs] def standardizeBarType(value): ''' Standardizes bar type names. Converts all names to lower case, None to 'regular', and 'light-light' to 'double' and 'light-heavy' to 'final', raises an error for unknown styles. ''' if value is None: return 'regular' # for now, return with string value = value.lower() if value in barTypeList: return value elif value in barTypeDict: return barTypeDict[value] # if not match else: raise BarException(f'cannot process style: {value}')
# ------------------------------------------------------------------------------
[docs] class Barline(base.Music21Object): ''' A representation of a barline. Barlines are conventionally assigned to Measure objects using the leftBarline and rightBarline attributes. >>> bl = bar.Barline('double') >>> bl <music21.bar.Barline type=double> The type can also just be set via a keyword of "type". Or if no type is specified, a regular barline is returned. Location can also be explicitly stored, but it's not needed except for musicxml translation: >>> bl2 = bar.Barline(type='dashed') >>> bl2 <music21.bar.Barline type=dashed> >>> bl3 = bar.Barline() >>> bl3 <music21.bar.Barline type=regular> >>> bl4 = bar.Barline(type='final', location='right') >>> bl4 <music21.bar.Barline type=final> >>> bl4.type 'final' Note that the barline type 'ticked' only is displayed correctly in Finale and Finale Notepad. N.B. for backwards compatibility reasons, currently Bar objects do not use the style.Style class since the phrase "style" was already used. ''' validStyles = list(barTypeDict.keys()) classSortOrder = -5 equalityAttributes = ('type', 'pause', 'location') def __init__(self, type=None, # pylint: disable=redefined-builtin location=None, **keywords): super().__init__(**keywords) self._type = None # same as style # this will raise an exception on error from property self.type = type # pause can be music21.expressions.Fermata object self.pause = None # location is primarily stored in the stream as leftBarline or rightBarline # but can also be stored here. self.location = location # musicxml values: can be left, right, middle, None def _reprInternal(self): return f'type={self.type}' def _getType(self): return self._type def _setType(self, value): self._type = standardizeBarType(value) type = property(_getType, _setType, doc=''' Get and set the Barline type property. >>> b = bar.Barline() >>> b.type = 'tick' >>> b.type 'tick' Synonyms are given for some types, based on musicxml styles: >>> b.type = 'light-light' >>> b.type 'double' ''')
[docs] def musicXMLBarStyle(self): ''' Returns the musicxml style for the bar. Most are the same as `.type` but "double" and "final" are different. >>> b = bar.Barline('tick') >>> b.musicXMLBarStyle() 'tick' >>> b.type = 'double' >>> b.musicXMLBarStyle() 'light-light' >>> b.type = 'final' >>> b.musicXMLBarStyle() 'light-heavy' * Changed in v5.7: was a property before. ''' return typeToMusicXMLBarStyle(self.type)
# ------------------------------------------------------------------------------ # note that musicxml permits the barline to have attributes for segno and coda # <xs:attribute name="segno" type="xs:token"/> # <xs:attribute name="coda" type="xs:token"/> # type <ending> in musicxml is used to mark different endings
[docs] class Repeat(repeat.RepeatMark, Barline): ''' A Repeat barline. The `direction` parameter can be one of `start` or `end`. An `end` followed by a `start` should be encoded as two `bar.Repeat` signs. >>> rep = bar.Repeat(direction='end', times=3) >>> rep <music21.bar.Repeat direction=end times=3> To apply a repeat barline assign it to either the `.leftBarline` or `.rightBarline` attribute of a measure. >>> m = stream.Measure() >>> m.leftBarline = bar.Repeat(direction='start') >>> m.rightBarline = bar.Repeat(direction='end') >>> m.insert(0.0, meter.TimeSignature('4/4')) >>> m.repeatAppend(note.Note('D--5'), 4) >>> p = stream.Part() >>> p.insert(0.0, m) >>> p.show('text') {0.0} <music21.stream.Measure 0 offset=0.0> {0.0} <music21.bar.Repeat direction=start> {0.0} <music21.meter.TimeSignature 4/4> {0.0} <music21.note.Note D--> {1.0} <music21.note.Note D--> {2.0} <music21.note.Note D--> {3.0} <music21.note.Note D--> {4.0} <music21.bar.Repeat direction=end> The method :meth:`~music21.stream.Part.expandRepeats` on a :class:`~music21.stream.Part` object expands the repeats, but does not update measure numbers. >>> q = p.expandRepeats() >>> q.show('text') {0.0} <music21.stream.Measure 0 offset=0.0> {0.0} <music21.bar.Barline type=double> {0.0} <music21.meter.TimeSignature 4/4> {0.0} <music21.note.Note D--> {1.0} <music21.note.Note D--> {2.0} <music21.note.Note D--> {3.0} <music21.note.Note D--> {4.0} <music21.bar.Barline type=double> {4.0} <music21.stream.Measure 0a offset=4.0> {0.0} <music21.bar.Barline type=double> {0.0} <music21.meter.TimeSignature 4/4> {0.0} <music21.note.Note D--> {1.0} <music21.note.Note D--> {2.0} <music21.note.Note D--> {3.0} <music21.note.Note D--> {4.0} <music21.bar.Barline type=double> ''' # _repeatDots = None # not sure what this is for; inherited from old modules def __init__(self, direction: str = 'start', times: int|None = None, **keywords): repeat.RepeatMark.__init__(self) if direction == 'start': barType = 'heavy-light' else: barType = 'final' Barline.__init__(self, type=barType, **keywords) self._direction: str|None = None # either start or end self._times: int|None = None # if an end, how many repeats # start is forward, end is backward in musicxml self.direction = direction # start, end self.times = times def _reprInternal(self): msg = f'direction={self.direction}' if self.times is not None: msg += f' times={self.times}' return msg @property def direction(self) -> str: ''' Get or set the direction of this Repeat barline. Can be start or end. TODO: show how changing direction changes type. ''' if self._direction is None: # pragma: no cover raise BarException('_direction unexpectedly None after initialization') return self._direction @direction.setter def direction(self, value: str): if value.lower() in ('start', 'end'): self._direction = value.lower() if self._direction == 'end': self.type = 'final' elif self._direction == 'start': self.type = 'heavy-light' else: raise BarException(f'cannot set repeat direction to: {value}') @property def times(self) -> int|None: ''' Get or set the "times" property of this barline. This defines how many times the repeat happens. A standard repeat repeats 2 times; values equal to or greater than 0 are permitted. A repeat of 0 skips the repeated passage. >>> lb = bar.Repeat(direction='start') >>> rb = bar.Repeat(direction='end') Only end expressions can have times: >>> lb.times = 3 Traceback (most recent call last): music21.bar.BarException: cannot set repeat times on a start Repeat >>> rb.times = 3 >>> rb.times = -3 Traceback (most recent call last): music21.bar.BarException: cannot set repeat times to a value less than zero: -3 ''' return self._times @times.setter def times(self, value: int|None) -> None: if value is None: self._times = None else: try: candidate = int(value) except ValueError: # pylint: disable:raise-missing-from raise BarException( f'cannot set repeat times to: {value!r}' ) if candidate < 0: raise BarException( f'cannot set repeat times to a value less than zero: {value}' ) if self.direction == 'start': raise BarException('cannot set repeat times on a start Repeat') self._times = candidate
[docs] def getTextExpression(self, prefix='', postfix='x'): ''' Return a configured :class:`~music21.expressions.TextExpressions` object describing the repeat times. Append this to the stream for annotation of repeat times. >>> rb = bar.Repeat(direction='end') >>> rb.times = 3 >>> rb.getTextExpression() <music21.expressions.TextExpression '3x'> >>> rb.getTextExpression(prefix='repeat ', postfix=' times') <music21.expressions.TextExpression 'repeat 3 t...'> ''' value = f'{prefix}{self._times}{postfix}' return expressions.TextExpression(value)
# ------------------------------------------------------------------------------ class Test(unittest.TestCase): def testCopyAndDeepcopy(self): from music21.test.commonTest import testCopyAll testCopyAll(self, globals()) def testSortOrder(self): from music21 import stream from music21 import clef from music21 import note from music21 import metadata m = stream.Measure() b = Repeat() m.leftBarline = b c = clef.BassClef() m.append(c) n = note.Note() m.append(n) # check sort order self.assertEqual(m[0], b) self.assertEqual(m[1], c) self.assertEqual(m[2], n) # if we add metadata, it sorts ahead of bar md = metadata.Metadata() m.insert(0, md) self.assertEqual(m[0], md) self.assertEqual(m[1], b) def testFreezeThaw(self): from music21 import converter from music21 import stream # pylint: disable=redefined-outer-name from music21.bar import Barline # avoid not same class error b = Barline() self.assertNotIn('StyleMixin', b.classes) s = stream.Stream([b]) data = converter.freezeStr(s, fmt='pickle') s2 = converter.thawStr(data) thawedBarline = s2[0] # Previously, raised AttributeError self.assertEqual(thawedBarline.hasStyleInformation, False) if __name__ == '__main__': import music21 music21.mainTest(Test)