from typing import Optional, Union
import abjad
def _generate_harmonics_from_pitch(fundamental: abjad.Pitch,
upper_bound: abjad.Pitch,
) -> list:
r"""Private function used by |auxjad.get.virtual_fundamental()|. Generates
the harmonic series for a given fundamental pitch up to a given upper
bound.
"""
harmonic_intervals = [12, 7, 5, 4, 3, 3, 2, 2, 2, 1, 2, 1, 2, 1, 1]
harmonics = [fundamental]
for interval in harmonic_intervals:
harmonic = harmonics[-1] + interval
if harmonic > upper_bound:
break
harmonics.append(harmonic)
else: # if upper bound not reached yet, harmonics go in semitones
while harmonics[-1] < upper_bound:
harmonic = harmonics[-1] + 1
harmonics.append(harmonic)
return harmonics
[docs]def virtual_fundamental(pitches: Union[abjad.PitchSegment, abjad.Chord],
*,
min_fundamental: Optional[abjad.Pitch] = None,
) -> abjad.NamedPitch:
r"""Returns the virtual fundamental (highest common fundamental) for all
pitches in a |abjad.PitchSegment| or |abjad.Chord|. Return value is of type
|abjad.NamedPitch|.
Basic usage:
This function will look for the highest fundamental whose harmonic
series contains all notes in a given collection of pitches. E.g.:
>>> pitches = abjad.PitchSegment(r"c'' g''")
>>> auxjad.get.virtual_fundamental(pitches)
c'
>>> pitches = abjad.PitchSegment(r"c'' e'' g''")
>>> auxjad.get.virtual_fundamental(pitches)
c
>>> pitches = abjad.PitchSegment(r"c'' f'' g''")
>>> auxjad.get.virtual_fundamental(pitches)
f,
>>> pitches = abjad.PitchSegment(r"c'' d'' ef'' fs''")
>>> auxjad.get.virtual_fundamental(pitches)
bf,,
.. note::
Auxjad automatically adds this function as an extension function to
|abjad.get|. It can thus be used from either |auxjad.get|_ or
|abjad.get| namespaces. Therefore, the two lines below are equivalent:
>>> pitches = abjad.PitchSegment(r"c'' g''")
>>> auxjad.get.virtual_fundamental(pitches)
c'
>>> abjad.get.virtual_fundamental(pitches)
c'
Input types:
Input types can be |abjad.PitchSegment| or |abjad.Chord|:
>>> pitches = abjad.PitchSegment(r"c'' cs'' d'' ef'' e'' fs''")
>>> auxjad.get.virtual_fundamental(pitches)
d,,
>>> chord = abjad.Chord(r"<c'' cs'' d'' ef'' e'' fs''>4")
>>> auxjad.get.virtual_fundamental(chord)
d,,
>>> staff = abjad.Staff(r"r4 <c'' cs'' d'' ef'' e'' fs''>4 r4")
>>> auxjad.get.virtual_fundamental(staff[1])
d,,
``min_fundamental``:
The partials above the 13th partial can be approximated to include all
chromatic pitches. This means that any complex of pitches will have a
virtual fundamental, even though they are fairly distant from it. To
limit the search of a fundamental by a minimum value, set
``min_fundamental`` to a specific pitch:
>>> pitches = abjad.PitchSegment(r"c'' cs'' d'' ef'' e'' fs''")
>>> auxjad.get.virtual_fundamental(
... pitches,
... min_fundamental=abjad.NamedPitch(r"c,,,"),
... )
d,,
>>> pitches = abjad.PitchSegment(r"c'' cs'' d'' ef'' e'' fs''")
>>> auxjad.get.virtual_fundamental(
... pitches,
... min_fundamental=abjad.NumberedPitch(-48),
... )
d,,
.. error::
If a fundamental is not found with a given ``min_fundamental``, the
function will raise a :exc:`ValueError` exception:
>>> pitches = abjad.PitchSegment(r"c'' cs'' d'' ef'' e'' fs''")
>>> auxjad.get.virtual_fundamental(
... pitches,
... min_fundamental=abjad.NamedPitch(r"c'"),
... )
ValueError: No fundamental found above c'
"""
if not isinstance(pitches, (abjad.PitchSegment, abjad.Chord)):
raise TypeError("argument must be 'abjad.PitchSegment' or an "
"'abjad.Chord'")
if min_fundamental is not None:
if not isinstance(min_fundamental, (abjad.Pitch)):
raise TypeError("'min_fundamental' must be 'abjad.Pitch' or child "
"class")
if isinstance(pitches, abjad.Chord):
pitches = pitches.written_pitches
fundamental = abjad.NumberedPitch(min(pitches))
if min_fundamental is not None:
min_fundamental = abjad.NumberedPitch(min_fundamental)
while True:
harmonics = _generate_harmonics_from_pitch(fundamental, max(pitches))
if all(abjad.NumberedPitch(pitch) in harmonics for pitch in pitches):
return abjad.NamedPitch(fundamental)
fundamental = fundamental - 1
if min_fundamental is not None and fundamental < min_fundamental:
raise ValueError('No fundamental found above '
+ format(min_fundamental))