Source code for auxjad.get.virtual_fundamental

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))