from typing import Optional, Union
from musicxml import XMLAccidental
from musicscore.musictree import MusicTree
from musicscore.xmlwrapper import XMLWrapper
__all__ = ['STANDARD', 'FLAT', 'SHARP', 'ENHARMONIC', 'FORCESHARP', 'FORCEFLAT', 'SIGNS', 'Accidental']
#:
STANDARD = {
0: ('C', 0, 0),
0.5: ('C', 0.5, 0),
1: ('C', 1, 0),
1.5: ('C', 1.5, 0),
2: ('D', 0, 0),
2.5: ('E', -1.5, 0),
3: ('E', -1, 0),
3.5: ('E', -0.5, 0),
4: ('E', 0, 0),
4.5: ('E', 0.5, 0),
5: ('F', 0, 0),
5.5: ('F', 0.5, 0),
6: ('F', 1, 0),
6.5: ('F', 1.5, 0),
7: ('G', 0, 0),
7.5: ('A', -1.5, 0),
8: ('A', -1, 0),
8.5: ('A', -0.5, 0),
9: ('A', 0, 0),
9.5: ('B', -1.5, 0),
10: ('B', -1, 0),
10.5: ('B', -0.5, 0),
11: ('B', 0, 0),
11.5: ('C', -0.5, 1)
}
#:
FLAT = {
0: ('C', 0, 0),
0.5: ('D', -1.5, 0),
1: ('D', -1, 0),
1.5: ('D', -0.5, 0),
2: ('D', 0, 0),
2.5: ('E', -1.5, 0),
3: ('E', -1, 0),
3.5: ('E', -0.5, 0),
4: ('E', 0, 0),
4.5: ('F', -0.5, 0),
5: ('F', 0, 0),
5.5: ('G', -1.5, 0),
6: ('G', -1, 0),
6.5: ('G', -0.5, 0),
7: ('G', 0, 0),
7.5: ('A', -1.5, 0),
8: ('A', -1, 0),
8.5: ('A', -0.5, 0),
9: ('A', 0, 0),
9.5: ('B', -1.5, 0),
10: ('B', -1, 0),
10.5: ('B', -0.5, 0),
11: ('B', 0, 0),
11.5: ('C', -0.5, 1)
}
#:
SHARP = {
0: ('C', 0, 0),
0.5: ('C', 0.5, 0),
1: ('C', 1, 0),
1.5: ('C', 1.5, 0),
2: ('D', 0, 0),
2.5: ('D', 0.5, 0),
3: ('D', 1, 0),
3.5: ('D', 1.5, 0),
4: ('E', 0, 0),
4.5: ('E', 0.5, 0),
5: ('F', 0, 0),
5.5: ('F', 0.5, 0),
6: ('F', 1, 0),
6.5: ('F', 1.5, 0),
7: ('G', 0, 0),
7.5: ('G', 0.5, 0),
8: ('G', 1, 0),
8.5: ('G', 1.5, 0),
9: ('A', 0, 0),
9.5: ('A', 0.5, 0),
10: ('A', 1, 0),
10.5: ('A', 1.5, 0),
11: ('B', 0, 0),
11.5: ('B', 0.5, 0)
}
#:
ENHARMONIC = {
0: ('C', 0, 0),
0.5: ('D', -1.5, 0),
1: ('D', -1, 0),
1.5: ('D', -0.5, 0),
2: ('D', 0, 0),
2.5: ('D', 0.5, 0),
3: ('D', 1, 0),
3.5: ('D', 1.5, 0),
4: ('E', 0, 0),
4.5: ('F', -0.5, 0),
5: ('F', 0, 0),
5.5: ('G', -1.5, 0),
6: ('G', -1, 0),
6.5: ('G', -0.5, 0),
7: ('G', 0, 0),
7.5: ('G', 0.5, 0),
8: ('G', 1, 0),
8.5: ('G', 1.5, 0),
9: ('A', 0, 0),
9.5: ('A', 0.5, 0),
10: ('A', 1, 0),
10.5: ('A', 1.5, 0),
11: ('B', 0, 0),
11.5: ('B', 0.5, 0)
}
#:
FORCESHARP = {
0: ('B', 1, -1),
0.5: ('B', 1.5, -1),
1: ('B', 2, -1),
1.5: ('C', 1.5, 0),
2: ('C', 2, 0),
2.5: ('D', 0.5, 0),
3: ('D', 1, 0),
3.5: ('D', 1.5, 0),
4: ('D', 2, 0),
4.5: ('E', 0.5, 0),
5: ('E', 1, 0),
5.5: ('E', 1.5, 0),
6: ('E', 2, 0),
6.5: ('F', 1.5, 0),
7: ('F', 2, 0),
7.5: ('G', 0.5, 0),
8: ('G', 1, 0),
8.5: ('G', 1.5, 0),
9: ('G', 2, 0),
9.5: ('A', 0.5, 0),
10: ('A', 1, 0),
10.5: ('A', 1.5, 0),
11: ('A', 2, 0),
11.5: ('B', 0.5, 0)
}
#:
FORCEFLAT = {
0: ('D', -2, 0),
0.5: ('D', -1.5, 0),
1: ('D', -1, 0),
1.5: ('D', -0.5, 0),
2: ('E', -2, 0),
2.5: ('E', -1.5, 0),
3: ('F', -2, 0),
3.5: ('F', -1.5, 0),
4: ('F', -1, 0),
4.5: ('F', -0.5, 0),
5: ('G', -2, 0),
5.5: ('G', -1.5, 0),
6: ('G', -1, 0),
6.5: ('G', -0.5, 0),
7: ('A', -2, 0),
7.5: ('A', -1.5, 0),
8: ('A', -1, 0),
8.5: ('A', -0.5, 0),
9: ('B', -2, 0),
9.5: ('B', -1.5, 0),
10: ('C', -2, 1),
10.5: ('C', -1.5, 1),
11: ('C', -1, 1),
11.5: ('C', -0.5, 1)
}
#:
SIGNS = {-2: 'flat-flat',
-1.5: 'three-quarters-flat',
-1: 'flat',
-0.5: 'quarter-flat',
0: 'natural',
0.5: 'quarter-sharp',
1: 'sharp',
1.5: 'three-quarters-sharp',
2: 'double-sharp'
}
[docs]class Accidental(MusicTree, XMLWrapper):
"""
Parent type: :obj:`~musicscore.midi.Midi`
Child type: None
Accidental is the class for managing :obj:`musicscore.midi.Midi`'s accidental sign and its pitch parameters: step, alter, octave.
The parameter mode ('standard', 'enharmonic', 'flat', 'sharp', 'force-flat', 'force-sharp') can be used to set different enharmonic variants of the same
pitch.
"""
_ATTRIBUTES = {'mode', 'show', 'parent_midi'}
XMLClass = XMLAccidental
def __init__(self, mode='standard', show: Optional[bool] = None, **kwargs):
super().__init__()
self._xml_object = self.XMLClass(value_='natural', **kwargs)
self._mode = None
self._show = None
self.show = show
self.mode = mode
def _update(self):
self._update_parent_midi()
self._update_xml_object()
def _update_parent_midi(self):
if self.parent_midi and self.parent_midi.value != 0:
self.parent_midi._update_pitch_parameters()
def _update_xml_object(self):
if self.sign:
self._xml_object.value_ = self.sign
@XMLWrapper.xml_object.getter
def xml_object(self) -> Optional[XMLClass]:
"""
If an attribute is not found directly in a :obj:`~musicscore.musictree.MusicTree` class which inherits this class, it wll be passed on to ``__get_attribute__`` and ``__set__attribute__`` methods of ``xml_object``.
It can also use the short cut attributes of :obj:`~musicxml.xmlelement.xmlelement.XMLElement` to get or set the first child.
:return: wrapped MusicXML element of type :obj:`XMLClass`
"""
if self.parent_midi and self.parent_midi.value == 0:
return None
if self.show is True:
return self._xml_object
elif self.show is False:
return None
@property
def mode(self):
"""
permitted modes: ``standard``, ``flat``, ``sharp``, ``enharmonic``, ``force-sharp``, ``force-flat``
:return: accidental mode which corresponds to global variables :obj:`~musicscore.accidental.STANDARD`, :obj:`~musicscore.accidental.FLAT`, :obj:`~musicscore.accidental.SHARP`,
:obj:`~musicscore.accidental.ENHARMONIC`, :obj:`~musicscore.accidental.FORCESHARP`, :obj:`~musicscore.accidental.FORCEFLAT`
"""
return self._mode
@mode.setter
def mode(self, value):
permitted = ('standard', 'flat', 'sharp', 'enharmonic', 'force-sharp', 'force-flat')
if value not in permitted:
raise TypeError(f'accidental_mode.value {value} must be in {permitted}')
self._mode = value
self._update_xml_object()
self._update_parent_midi()
@property
def parent_midi(self) -> 'Midi':
"""
:return: The midi parent of Accidental in the :obj:`~musicscore.musictree.MusicTree` structure. It is equivalent to :obj:`up` or :obj:`get_parent()`
:rtype: :obj:`musicscore.midi.Midi`
"""
return self.up
@parent_midi.setter
def parent_midi(self, val):
val.add_child(self)
@property
def sign(self) -> Optional[str]:
"""
Converts ``alter`` parameter of :obj:`~get_pitch_parameters` into an actual ``sign`` depending on :obj:`~mode`. :obj:`~parent_midi` must be set first.
:return: Possible values: ``flat-flat``, ``three-quarters-flat``, ``flat``, ``quarter-flat``, ``natural``,
``quarter-sharp``, ``sharp``, ``three-quarters-sharp``, ``double-sharp``
"""
try:
alter = self.get_pitch_parameters()[1]
return SIGNS[alter]
except TypeError:
return None
@property
def show(self) -> bool:
"""
If ``False`` ``xml_object`` will be ``None`` and no accidental is shown in the :obj:`~musicscore.score.Score`.
If ``True`` the accidental is shown. If the value of one of the following xml_object :obj:`~musicxml.xmlelement.xmlelement.XMLAccidental` properties:``self.parentheses``, ``self.editorial``.
``self.cautionary`` or ``self.bracket`` is set to yes, the accidental is always shown regardless of this
property
"""
if 'yes' in [self.parentheses, self.editorial, self.cautionary, self.bracket]:
return True
return self._show
@show.setter
def show(self, val):
if val is not None and not isinstance(val, bool):
raise TypeError
if val != self._show:
self._show = val
try:
self.up.up._update_xml_accidental()
except AttributeError:
pass
[docs] def get_pitch_parameters(self, midi_value: Optional[Union[int, float]] = None) -> Optional[tuple]:
"""
:param: a valid midi value or ``None``. If ``midi_value == 0`` (for a rest) return value is ``None``. If ``midi_value is None`` and a :obj:`~parent_midi` exists, parent_midi's value will be used.
:return: A tuple consisting of pitch step name, alter value and octave value: ``(step, alter, octave)``
.. seealso::
:obj:`~mode`
"""
if midi_value is None:
if self.parent_midi:
midi_value = self.parent_midi.value
else:
return None
if midi_value == 0:
return None
if self.mode == 'standard':
output = STANDARD[midi_value % 12]
elif self.mode == 'enharmonic':
output = ENHARMONIC[midi_value % 12]
elif self.mode == 'force-sharp':
output = FORCESHARP[midi_value % 12]
elif self.mode == 'force-flat':
output = FORCEFLAT[midi_value % 12]
elif self.mode == 'flat':
output = FLAT[midi_value % 12]
elif self.mode == 'sharp':
output = SHARP[midi_value % 12]
else:
raise ValueError
return output[0], output[1], output[2] + (int(midi_value // 12)) - 1
def __copy__(self):
return self.__class__(mode=self.mode, show=self.show)