class MusicMaker:

    def __init__(
        self, 
        counts, 
        denominator, 
        pitches,
        clef,
        ):
        self.counts = counts
        self.denominator = denominator
        self.pitches = pitches
        self.clef = clef

    def make_notes_and_rests(self, counts, denominator, time_signatures, clef):
        """
        Makes notes and rests.
        """
        durations = [_.duration for _ in time_signatures]
        total_duration = sum(durations)
        talea = rmakers.Talea(counts, denominator)
        talea_index = 0
        leaves = []
        current_duration = abjad.Duration(0)
        while current_duration < total_duration:
            leaf_duration = talea[talea_index]
            if 0 < leaf_duration: 
                pitch = abjad.NamedPitch("c'")
            else:
                pitch = None
                leaf_duration = abs(leaf_duration)
            if total_duration < (leaf_duration + current_duration):
                leaf_duration = total_duration - current_duration
            leaves_ = abjad.LeafMaker()([pitch], [leaf_duration])
            leaves.extend(leaves_)
            current_duration += leaf_duration
            talea_index += 1
        staff = abjad.Staff(leaves)
        clef = abjad.Clef(clef)
        abjad.attach(clef, staff[0])
        return staff

    def impose_time_signatures(self, staff, time_signatues):
        """
        Imposes time signatures.
        """
        selections = abjad.mutate.split(staff[:], time_signatures, cyclic=True)
        for time_signature, selection in zip(time_signatures, selections):
            abjad.attach(time_signature, selection[0])
        measure_selections = abjad.select(staff).leaves().group_by_measure()
        for time_signature, measure_selection in zip(time_signatures, measure_selections):
            abjad.Meter.rewrite_meter(measure_selection, time_signature)

    def pitch_notes(self, staff, pitches):
        """
        Pitches notes.
        """
        pitches = abjad.CyclicTuple(pitches)
        plts = abjad.select(staff).logical_ties(pitched=True)
        for i, plt in enumerate(plts):
            pitch = pitches[i]
            for note in plt:
                note.written_pitch = pitch

    def attach_indicators(self, staff):
        """
        Attaches indicators to runs.
        """
        for selection in abjad.select(staff).runs():
            articulation = abjad.Articulation("accent")
            abjad.attach(articulation, selection[0])
            if 3 <= len(selection):
                abjad.hairpin("p < f", selection)

            else:
                dynamic = abjad.Dynamic("ppp")
                abjad.attach(dynamic, selection[0])
        abjad.override(staff).dynamic_line_spanner.staff_padding = 4

    def make_staff(self, time_signatures):
        """
        Makes staff.
        """
        staff = self.make_notes_and_rests(
            self.counts,
            self.denominator,
            time_signatures,
            self.clef
            )
        self.impose_time_signatures(staff, time_signatures)
        self.pitch_notes(staff, self.pitches)
        self.attach_indicators(staff)
        return staff

"""
Explanation: Encapsulation, part 2: classes
In the previous notebook we encapsulated our code in functions. Functions model programming tasks as a collection of verbs (actions): data flows into and out of a series of functions until the desired result has been achieved. Classes, on the other hand, model programming tasks as a collection of nouns (objects). Objects have data (attributes) and implement methods to modify the data they contain. In this notebook we'll encapsulate our music-generating functions in a class.
1. The class definition
The code below defines a class. An object-oriented class is like a template that tells a programming language how to construct instances of itself. ("Class instance" and "object" mean the same thing in an object-oriented context.) This means that after Python reads our music-maker class definition, we can instantiate as many music-maker objects as we want. (More on this below.) The four functions we defined in the previous notebook correspond to the four methods defined here. Functions and methods are both introduced with Python's def keyword. The primary difference between functions and methods is that functions can be defined at the top level of a module while methods are always defined within (and "bound to") a class. Classes provide an even higher level of encapsulation than functions, because classes encapsulate methods.
End of explanation
"""


pairs = [(3, 4), (5, 16), (3, 8), (4, 4)]
time_signatures = [abjad.TimeSignature(_) for _ in pairs]
counts = [1, 2, -3, 4]
denominator = 16
string = "d' fs' a' d'' g' ef'"
pitches = abjad.CyclicTuple(string.split())
clef = "treble"
maker = MusicMaker(counts, denominator, pitches, clef)

"""
Explanation: 2. Instantiating objects
We can now instantiate a music-maker object. We do this by calling the music-maker's initializer, to which we pass counts, denominators and pitches:
End of explanation
"""


staff = maker.make_staff(time_signatures)
abjad.show(staff)

"""
Explanation: Finally pass in time signatures and ask our music-maker to make a staff:
End of explanation
"""


fast_music_maker = MusicMaker(
    counts=[1, 1, 1, 1, 1, -1],
    denominator=16,
    pitches=[0, 1],
    clef="treble"
)

slow_music_maker = MusicMaker(
    counts=[3, 4, 5, -1],
    denominator=4,
    pitches=["b,", "bf,", "gf,"],
    clef="bass",
)

stuttering_music_maker = MusicMaker(
    counts=[1, 1, -7],
    denominator=16,
    pitches=[23],
    clef="treble"
)

sparkling_music_maker = MusicMaker(
    counts=[1, -5, 1, -9, 1, -5],
    denominator=16,
    pitches=[38, 39, 40],
    clef="treble^8",
)

"""
Explanation: 3. Making musical texture with multiple instances of a single class
Because we can create multiple, variously initialized instances of the same class, it's possible to create both minimal and varied a polyphonic textures with just a single class definition. First we initialize four different makers:
End of explanation
"""


upper_staff = abjad.Staff()
lower_staff = abjad.Staff()
pairs = [(3, 4), (5, 16), (3, 8), (4, 4)]
time_signatures = [abjad.TimeSignature(_) for _ in pairs]

"""
Explanation: Let's use these four music-makers to create a duo. We can set up a score with two staves and generate the music according to a single set of time signatures:
End of explanation
"""


makers = (
    fast_music_maker,
    slow_music_maker,
    stuttering_music_maker,
    sparkling_music_maker,
)

for maker in makers:
    staff = maker.make_staff(time_signatures)
    selection = staff[:]
    staff[:] = []
    upper_staff.extend(selection)

makers = (
    slow_music_maker,
    slow_music_maker,
    stuttering_music_maker,
    fast_music_maker,
)
    
for maker in makers:
    staff = maker.make_staff(time_signatures)
    selection = staff[:]
    staff[:] = []
    lower_staff.extend(selection)

"""
Explanation: Next, we loop through four makers, appending each maker's music to our staves as we go. We'll generate music for the top and bottom staff independently:
End of explanation
"""


piano_staff = abjad.StaffGroup(
    [upper_staff, lower_staff],
    lilypond_type="PianoStaff",
)
abjad.override(upper_staff).dynamic_line_spanner.staff_padding = 4
abjad.override(lower_staff).dynamic_line_spanner.staff_padding = 4
score = abjad.Score([piano_staff])
bar_line = abjad.BarLine("|.")
last_leaf = abjad.select(lower_staff).leaf(-1)
abjad.attach(bar_line, last_leaf)

lilypond_file = abjad.LilyPondFile.new(score)
lilypond_file.header_block.composer = "Abjad Summer Course"
string = r"\markup \fontsize #3 \bold ENCAPSULATION"
title_markup = abjad.Markup(string, literal=True)
lilypond_file.header_block.title = title_markup
lilypond_file.header_block.subtitle = "working with classes"
abjad.show(lilypond_file)

"""
Explanation: 4. Making the score
Now we can make our final score and add some formatting:
End of explanation
"""


