1# -*- coding: utf-8 -*- 2# ------------------------------------------------------------------------------ 3# Name: articulations.py 4# Purpose: music21 classes for representing articulations 5# 6# Authors: Michael Scott Cuthbert 7# Christopher Ariza 8# 9# Copyright: Copyright © 2009-2013 Michael Scott Cuthbert and the music21 Project 10# License: BSD, see license.txt 11# ------------------------------------------------------------------------------ 12 13''' 14Classes for representing and processing articulations. 15Specific articulations are modeled as :class:`~music21.articulation.Articulation` subclasses. 16 17A :class:`~music21.note.Note` object has a :attr:`~music21.note.Note.articulations` attribute. 18This list can be used to store one or more :class:`music21.articulation.Articulation` subclasses. 19 20As much as possible, MusicXML names are used for Articulation classes, 21with xxx-yyy changed to XxxYyy. For instance, "strong-accent" in 22MusicXML is "StrongAccent" here. 23 24Fingering and other playing marks are found here. Fermatas, trills, etc. 25are found in music21.expressions. 26 27 28 29>>> n1 = note.Note('D#4') 30>>> n1.articulations.append(articulations.Tenuto()) 31>>> #_DOCS_SHOW n1.show() 32 33>>> c1 = chord.Chord(['C3', 'G4', 'E-5']) 34>>> c1.articulations = [articulations.OrganHeel(), articulations.Accent()] 35>>> #_DOCS_SHOW c1.show() 36 37A longer test showing the utility of the module: 38 39>>> s = stream.Stream() 40>>> n1 = note.Note('c#5') 41>>> n1.articulations = [articulations.Accent()] 42>>> n1.quarterLength = 1.25 43>>> s.append(n1) 44 45>>> n2 = note.Note('d5') 46>>> n2.articulations = [articulations.StrongAccent()] 47>>> n2.quarterLength = 0.75 48>>> s.append(n2) 49 50>>> n3 = note.Note('b4') 51>>> n3.articulations = [articulations.Staccato()] 52>>> n3.quarterLength = 1.25 53>>> n3.tie = tie.Tie('start') 54>>> s.append(n3) 55 56>>> n4 = note.Note('b4') 57>>> n4.articulations = [articulations.Staccatissimo()] 58>>> n4.quarterLength = 0.75 59>>> s.append(n4) 60 61>>> n5 = note.Note('a4') 62>>> n5.articulations = [articulations.Tenuto()] 63>>> n5.quarterLength = 4/3 64>>> s.append(n5) 65 66>>> n6 = note.Note('b-4') 67>>> n6.articulations = [articulations.Staccatissimo(), articulations.Tenuto()] 68>>> n6.quarterLength = 2/3 69>>> s.append(n6) 70 71>>> s.metadata = metadata.Metadata() 72>>> s.metadata.title = 'Prova articolazioni' # ital: 'Articulation Test' 73>>> s.metadata.composer = 'Giuliano Lancioni' 74 75>>> #_DOCS_SHOW s.show() 76 77.. image:: images/prova_articolazioni.* 78 :width: 628 79 80''' 81from typing import Optional 82import unittest 83 84from music21 import base 85from music21 import common 86from music21.common.classTools import tempAttribute 87from music21 import exceptions21 88from music21 import environment 89from music21 import style 90 91_MOD = 'articulations' 92environLocal = environment.Environment(_MOD) 93 94 95 96class ArticulationException(exceptions21.Music21Exception): 97 pass 98 99# ------------------------------------------------------------------------------ 100class Articulation(base.Music21Object): 101 ''' 102 Base class for all Articulation sub-classes. 103 104 >>> x = articulations.Articulation() 105 >>> x.placement = 'below' 106 >>> x.style.absoluteY = 20 107 >>> x.displayText = '>' 108 109 ''' 110 _styleClass = style.TextStyle 111 112 def __init__(self): 113 super().__init__() 114 self.placement = None 115 # declare a unit interval shift for the performance of this articulation 116 self._volumeShift: float = 0.0 117 self.lengthShift: float = 1.0 118 self.tieAttach: str = 'first' # attach to first or last or all notes after split 119 self.displayText: Optional[str] = None 120 121 def _reprInternal(self): 122 return '' 123 124 @property 125 def name(self) -> str: 126 ''' 127 returns the name of the articulation, which is generally the 128 class name without the leading letter lowercase. 129 130 Subclasses can override this as necessary. 131 132 >>> st = articulations.Staccato() 133 >>> st.name 134 'staccato' 135 136 >>> sp = articulations.SnapPizzicato() 137 >>> sp.name 138 'snap pizzicato' 139 ''' 140 className = self.__class__.__name__ 141 return common.camelCaseToHyphen(className, replacement=' ') 142 143 # def __eq__(self, other): 144 # ''' 145 # Equality. Based only on the class name, 146 # as other other attributes are independent of context and deployment. 147 # 148 # 149 # >>> at1 = articulations.StrongAccent() 150 # >>> at2 = articulations.StrongAccent() 151 # >>> at1.placement = 'above' 152 # >>> at2.placement = 'below' 153 # >>> at1 == at2 154 # True 155 # 156 # 157 # Comparison between classes and with the object itself behaves as expected 158 # 159 # 160 # >>> at3 = articulations.Accent() 161 # >>> at4 = articulations.Staccatissimo() 162 # >>> at1 == at3 163 # False 164 # >>> at4 == at4 165 # True 166 # 167 # 168 # OMIT_FROM_DOCS 169 # 170 # >>> at5 = articulations.Staccato() 171 # >>> at6 = articulations.Spiccato() 172 # >>> [at1, at4, at3] == [at1, at4, at3] 173 # True 174 # >>> [at1, at2, at3] == [at2, at3, at1] 175 # False 176 # >>> set([at1, at2, at3]) == set([at2, at3, at1]) 177 # True 178 # >>> at6 == None 179 # False 180 # ''' 181 # # checks pitch.octave, pitch.accidental, uses Pitch.__eq__ 182 # if other == None or not isinstance(other, Articulation): 183 # return False 184 # elif self.__class__ == other.__class__: 185 # return True 186 # return False 187 # 188 189 def _getVolumeShift(self): 190 return self._volumeShift 191 192 def _setVolumeShift(self, value): 193 # value should be between 0 and 1 194 if value > 1: 195 value = 1 196 elif value < -1: 197 value = -1 198 self._volumeShift = value 199 200 volumeShift = property(_getVolumeShift, _setVolumeShift, doc=''' 201 Get or set the volumeShift of this Articulation. This value, between -1 and 1, 202 that is used to shift the final Volume of the object it is attached to. 203 204 205 >>> at1 = articulations.StrongAccent() 206 >>> at1.volumeShift > 0.1 207 True 208 ''') 209 210# ------------------------------------------------------------------------------ 211class LengthArticulation(Articulation): 212 ''' 213 Superclass for all articulations that change the length of a note. 214 ''' 215 def __init__(self): 216 super().__init__() 217 self.tieAttach = 'last' 218 219class DynamicArticulation(Articulation): 220 ''' 221 Superclass for all articulations that change the dynamic of a note. 222 ''' 223 224class PitchArticulation(Articulation): 225 ''' 226 Superclass for all articulations that change the pitch of a note. 227 ''' 228 229class TimbreArticulation(Articulation): 230 ''' 231 Superclass for all articulations that change the timbre of a note. 232 ''' 233 234 235# ------------------------------------------------------------------------------ 236class Accent(DynamicArticulation): 237 ''' 238 239 >>> a = articulations.Accent() 240 ''' 241 def __init__(self): 242 super().__init__() 243 self._volumeShift = 0.1 244 245 246class StrongAccent(Accent): 247 ''' 248 Like an accent but even stronger. Has an extra 249 attribute of pointDirection 250 251 >>> a = articulations.StrongAccent() 252 >>> a.pointDirection 253 'up' 254 >>> a.pointDirection = 'down' 255 >>> a.pointDirection 256 'down' 257 ''' 258 def __init__(self): 259 super().__init__() 260 self._volumeShift = 0.15 261 self.pointDirection = 'up' 262 263class Staccato(LengthArticulation): 264 ''' 265 266 >>> a = articulations.Staccato() 267 ''' 268 def __init__(self): 269 super().__init__() 270 self._volumeShift = 0.05 271 self.lengthShift = 0.7 272 273class Staccatissimo(Staccato): 274 ''' 275 A very short note (derived from staccato), usually 276 represented as a wedge. 277 278 >>> a = articulations.Staccatissimo() 279 ''' 280 def __init__(self): 281 super().__init__() 282 self._volumeShift = 0.05 283 self.lengthShift = 0.5 284 285class Spiccato(Staccato, Accent): 286 ''' 287 A staccato note + accent in one 288 289 >>> spiccato = articulations.Spiccato() 290 >>> staccato = articulations.Staccato() 291 >>> accent = articulations.Accent() 292 >>> spiccato.lengthShift == staccato.lengthShift 293 True 294 >>> spiccato.volumeShift == accent.volumeShift 295 True 296 ''' 297 def __init__(self): 298 Staccato.__init__(self) 299 with tempAttribute(self, 'lengthShift'): 300 Accent.__init__(self) # order matters... 301 302 303class Tenuto(LengthArticulation): 304 ''' 305 >>> a = articulations.Tenuto() 306 ''' 307 def __init__(self): 308 super().__init__() 309 self._volumeShift = -0.05 # is this the right thing to do? 310 self.lengthShift = 1.1 311 312class DetachedLegato(LengthArticulation): 313 ''' 314 >>> a = articulations.DetachedLegato() 315 ''' 316 def __init__(self): 317 super().__init__() 318 self.lengthShift = 0.9 319 320# --------- indeterminate slides 321 322class IndeterminateSlide(PitchArticulation): 323 ''' 324 Represents a whole class of slides that are 325 of an indeterminate pitch amount (scoops, plops, etc.) 326 327 All these have style information of .style.lineShape 328 .style.lineType, .style.dashLength, and .style.spaceLength 329 ''' 330 _styleClass = style.LineStyle 331 332 333class Scoop(IndeterminateSlide): 334 ''' 335 An indeterminateSlide coming before the main note and going up 336 337 >>> a = articulations.Scoop() 338 ''' 339 340 341class Plop(IndeterminateSlide): 342 ''' 343 An indeterminateSlide coming before the main note and going down. 344 345 >>> a = articulations.Plop() 346 ''' 347 348class Doit(IndeterminateSlide): 349 ''' 350 An indeterminateSlide coming after the main note and going up. 351 352 >>> a = articulations.Doit() 353 ''' 354 def __init__(self): 355 super().__init__() 356 self.tieAttach = 'last' 357 358class Falloff(IndeterminateSlide): 359 ''' 360 An indeterminateSlide coming after the main note and going down. 361 362 >>> a = articulations.Falloff() 363 ''' 364 def __init__(self): 365 super().__init__() 366 self.tieAttach = 'last' 367 368# --------- end indeterminate slide 369 370 371class BreathMark(LengthArticulation): 372 ''' 373 Can have as a symbol 'comma' or 'tick' or None 374 375 >>> a = articulations.BreathMark() 376 >>> a.symbol = 'comma' 377 ''' 378 def __init__(self): 379 super().__init__() 380 self.lengthShift = 0.7 381 self.symbol = None 382 383class Caesura(Articulation): 384 ''' 385 >>> a = articulations.Caesura() 386 ''' 387 388class Stress(DynamicArticulation, LengthArticulation): 389 ''' 390 An articulation indicating stress. Played a little longer and louder. 391 392 >>> a = articulations.Stress() 393 ''' 394 def __init__(self): 395 super().__init__() 396 self._volumeShift = 0.05 397 self.lengthShift = 1.1 398 399class Unstress(DynamicArticulation): 400 ''' 401 An articulation indicating lack of stress. Played a little quieter. 402 403 >>> a = articulations.Unstress() 404 ''' 405 def __init__(self): 406 super().__init__() 407 self._volumeShift = -0.05 408 409 410# ------------------------------------------------------------------------------ 411class TechnicalIndication(Articulation): 412 ''' 413 TechnicalIndications (MusicXML: technical) give performance 414 indications specific to different instrument types, such 415 as harmonics or bowing. 416 417 TechnicalIndications can include an optional content. 418 ''' 419 420class Harmonic(TechnicalIndication): 421 ''' 422 A general harmonic indicator -- StringHarmonic is probably what you want... 423 ''' 424 425class Bowing(TechnicalIndication): 426 ''' 427 Indication that bowing is being affected. 428 429 >>> a = articulations.Bowing() 430 ''' 431 432class Fingering(TechnicalIndication): 433 ''' 434 Fingering is a technical indication that covers the fingering of 435 a note (in a guitar/fret context, this covers the fret finger, 436 see FrettedPluck for that). 437 438 Converts the MusicXML -- <fingering> object 439 440 >>> f = articulations.Fingering(5) 441 >>> f 442 <music21.articulations.Fingering 5> 443 >>> f.fingerNumber 444 5 445 446 `.substitution` indicates that this fingering indicates a substitute fingering: 447 448 >>> f.substitution = True 449 450 MusicXML distinguishes between a substitution and an alternate 451 fingering: 452 453 >>> f.alternate = True 454 455 Fingerings are the only articulations that apply per note in a chord. 456 Other articulations, e.g., accents, apply to the whole chord and will, 457 therefore, only be associated with the first note of a chord when serializing. 458 Since chords store all articulations in an ordered list, Fingerings 459 are mapped implicitly to the notes of a chord in order. Superfluous 460 Fingerings will be ignored and may be discarded when serializing. 461 ''' 462 def __init__(self, fingerNumber=None): 463 super().__init__() 464 self.fingerNumber = fingerNumber 465 self.substitution = False 466 self.alternate = False 467 468 def _reprInternal(self): 469 return str(self.fingerNumber) 470 471 472# ------------------------------------------------------------------------------ 473class UpBow(Bowing): 474 ''' 475 >>> a = articulations.UpBow() 476 ''' 477 478class DownBow(Bowing): 479 ''' 480 >>> a = articulations.DownBow() 481 ''' 482 483class StringHarmonic(Bowing, Harmonic): 484 ''' 485 Indicates that a note is a harmonic, and can also specify 486 whether it is the base pitch, the sounding pitch, or the touching pitch. 487 488 >>> h = articulations.StringHarmonic() 489 >>> h.harmonicType 490 'natural' 491 >>> h.harmonicType = 'artificial' 492 493 pitchType can be 'base', 'sounding', or 'touching' or None 494 495 >>> h.pitchType = 'base' 496 ''' 497 def __init__(self): 498 super().__init__() 499 self.harmonicType = 'natural' 500 self.pitchType = None 501 502class OpenString(Bowing): 503 pass 504 505class StringIndication(Bowing): 506 ''' 507 StringIndication indicates which string a note is played on. 508 509 A StringIndication can be constructed as 510 511 >>> si = articulations.StringIndication(2) 512 >>> si 513 <music21.articulations.StringIndication 2> 514 >>> si.number 515 2 516 517 If no argument to the constructor is specified, number defaults to 0. 518 ''' 519 def __init__(self, number=0): 520 super().__init__() 521 self.number = number 522 523 def _reprInternal(self): 524 return f'{self.number}' 525 526 527class StringThumbPosition(Bowing): 528 ''' 529 MusicXML -- thumb-position 530 ''' 531 pass 532 533class StringFingering(StringIndication, Fingering): 534 ''' 535 Indicates a fingering on a specific string. Nothing special for now. 536 ''' 537 pass 538 539class Pizzicato(Bowing): 540 ''' 541 in MusicXML, Pizzicato is an element of every note. 542 Here we represent pizzicatos along with all bowing marks. 543 544 For pluck, see FrettedPluck. 545 ''' 546 pass 547 548class SnapPizzicato(Pizzicato): 549 pass 550 551class NailPizzicato(Pizzicato): 552 ''' 553 Does not exist in MusicXML 554 ''' 555 pass 556 557class FretIndication(TechnicalIndication): 558 ''' 559 FretIndication indicates which fret of a string a note is played on. 560 561 A FretIndication can be constructed as 562 563 >>> fi = articulations.FretIndication(3) 564 >>> fi 565 <music21.articulations.FretIndication 3> 566 >>> fi.number 567 3 568 569 If no argument to the constructor is specified, number defaults to 0. 570 ''' 571 def __init__(self, number=0): 572 super().__init__() 573 self.number = number 574 575 def _reprInternal(self): 576 return f'{self.number}' 577 578class FrettedPluck(FretIndication, Fingering): 579 ''' 580 specifies plucking fingering for fretted instruments 581 582 pluck in musicxml 583 ''' 584 pass 585 586class HammerOn(FretIndication): 587 pass 588 589class PullOff(FretIndication): 590 pass 591 592class FretBend(FretIndication): 593 bendAlter = None # music21.interval.Interval object 594 preBend = None 595 release = None 596 withBar = None 597 598class FretTap(FretIndication): 599 pass 600 601class WindIndication(TechnicalIndication): 602 pass 603 604class WoodwindIndication(WindIndication): 605 pass 606 607class BrassIndication(WindIndication): 608 pass 609 610class TonguingIndication(WindIndication): 611 pass 612 613class DoubleTongue(TonguingIndication): 614 pass 615 616class TripleTongue(TonguingIndication): 617 pass 618 619class Stopped(WindIndication): 620 pass 621 622# ------------------------------- 623class OrganIndication(TechnicalIndication): 624 ''' 625 Indicates whether a pitch should be played with heel or toe. 626 627 Has one attribute, "substitution" default to False, which 628 indicates whether the mark is a substitution mark 629 ''' 630 def __init__(self): 631 super().__init__() 632 self.substitution = False 633 634 635class OrganHeel(OrganIndication): 636 pass 637 638class OrganToe(OrganIndication): 639 pass 640 641class HarpIndication(TechnicalIndication): 642 pass 643 644class HarpFingerNails(HarpIndication): 645 ''' 646 musicXML -- fingernails 647 ''' 648 pass 649 650class HandbellIndication(TechnicalIndication): 651 ''' 652 displayText is used to store any of the techniques in handbell music. 653 654 Values are damp, echo, gyro, hand martellato, mallet lift, 655 mallet table, martellato, martellato lift, 656 muted martellato, pluck lift, and swing 657 ''' 658 pass 659 660 661# ------------------------------------------------------------------------------ 662class Test(unittest.TestCase): 663 664 def testBasic(self): 665 a = FretBend() 666 self.assertEqual(a.bendAlter, None) 667 668 669# def testArticulationEquality(self): 670# a1 = Accent() 671# a2 = Accent() 672# a3 = StrongAccent() 673# a4 = StrongAccent() 674# 675# self.assertEqual(a1, a2) 676# self.assertEqual(a3, a4) 677# 678# # in order lists 679# self.assertEqual([a1, a3], [a2, a4]) 680# 681# self.assertEqual(set([a1, a3]), set([a1, a3])) 682# self.assertEqual(set([a1, a3]), set([a3, a1])) 683# 684# # comparison of sets of different objects do not pass 685# # self.assertEqual(list(set([a1, a3])), list(set([a2, a4]))) 686 687 688# ------------------------------------------------------------------------------ 689# define presented order in documentation 690_DOC_ORDER = [Articulation] 691 692if __name__ == '__main__': 693 import music21 694 music21.mainTest(Test) 695 696