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