In :import abjad %load_ext abjad.ext.ipython
In our last notebook, we saw what can be done with multiple instances of a single custom class. In this notebook, we will define a second custom class and demonstrate one way it might interact with instances of our first class: one class instance gets passed into another as an optional keyword argument.
First, we define a second custom class that will traverse a selection of music and attach indicators and/or spanners to it. We will give each instance of this class (1) the kind of attachment (indicator or spanner) to make, and (2) a selector that will determine how the attacher will traverse the music to make the attachments.
N. B. We also need to import Python's
copy module, because our class needs to generate a new slur, accent, etc. for each attachment.
In :import copy class AttachmentMaker(object): """ An attachment-making machine. """ def __init__(self, attachment, selector): self.attachment = attachment self.selector = selector def __call__(self, music): for selection in self.selector(music): attachment = copy.copy(self.attachment) abjad.attach(attachment, selection)
Let's add a new keyword argument to our existing
MusicMaker() class's initializer, so that we will be newly able to pass in
AttachmentMakere() instances to instances of
MusicMaker(). We will also add a new method to the class that passes the music into the
AttachmentMaker() instance to add the attachments to the music.
Note that instead of the
make_music() method we had before, we've made instances of
MusicMaker() callable; that is, to make music, we call the class instance as if it were a function, i.e.
musicmaker_instance(). As before, we pass our time signature pairs into the instance when we call it, to generate the music.
Notice also that we've prefixed the class definition's methods with initial underscores. This indicates that these methods are used internally by the class but that the user should not access and use the methods.
And finally, here is the modified
MusicMaker() class, including (1) private methods, (2) a new keyword argument for
AttachmentMaker() instances, and (3) a new method that allows
AttachmentMaker() instances to attach indicators and spanners to the generated music.
In :class MusicMaker(object): """ A music-making machine. """ def __init__( self, counts, denominator, pitches, attachment_makers=None, ): self.counts = counts self.denominator = denominator self.pitches = pitches self.attachment_makers = attachment_makers or  def __call__(self, time_signature_pairs): music = self._make_basic_rhythm( time_signature_pairs, self.counts, self.denominator, ) music = self._clean_up_rhythm(music, time_signature_pairs) music = self._add_pitches(music, self.pitches) music = self._add_attachments(music) return music def _make_basic_rhythm(self, time_signature_pairs, counts, denominator): """ Make a basic rhythm using ``time_signature_pairs``, ``counts`` and ``denominator``. """ total_duration = sum( abjad.Duration(pair) for pair in time_signature_pairs ) talea = abjad.rhythmmakertools.Talea( counts=counts, denominator=denominator, ) talea_index = 0 all_leaves =  current_duration = abjad.Duration(0) while current_duration < total_duration: leaf_duration = talea[talea_index] if leaf_duration > 0: pitch = abjad.NamedPitch("c'") else: pitch = None leaf_duration = abs(leaf_duration) if (leaf_duration + current_duration) > total_duration: leaf_duration = total_duration - current_duration current_leaves = abjad.LeafMaker()([pitch], [leaf_duration]) all_leaves.extend(current_leaves) current_duration += leaf_duration talea_index += 1 music = abjad.Container(all_leaves) return music def _clean_up_rhythm(self, music, time_signature_pairs): """ Clean up rhythms in ``music`` via ``time_signature_pairs``. """ shards = abjad.mutate(music[:]).split(time_signature_pairs) for i, shard in enumerate(shards): time_signature_pair = time_signature_pairs[i] measure = abjad.Measure(time_signature_pair) assert shard.get_duration() == abjad.Duration( time_signature_pair) abjad.mutate(shard).wrap(measure) return music def _add_pitches(self, music, pitches): """ Add ``pitches`` to music. """ pitches = abjad.CyclicTuple(pitches) logical_ties = abjad.iterate(music).by_logical_tie(pitched=True) for i, logical_tie in enumerate(logical_ties): pitch = pitches[i] for note in logical_tie: note.written_pitch = pitch return music def _add_attachments(self, music): """ Add attachments to ``music``. """ for attachment_maker in self.attachment_makers: attachment_maker(music) return music
In :slur_attachment_maker = AttachmentMaker( attachment=abjad.Slur(), selector=abjad.select().by_leaf().by_run((abjad.Note, abjad.Chord)) ) accent_attachment_maker = AttachmentMaker( attachment=abjad.Articulation('accent'), selector=abjad.select().by_leaf().by_run((abjad.Note, abjad.Chord)) )
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-2-f2399eef2fb5> in <module> ----> 1 slur_attachment_maker = AttachmentMaker( 2 attachment=abjad.Slur(), 3 selector=abjad.select().by_leaf().by_run((abjad.Note, abjad.Chord)) 4 ) 5 NameError: name 'AttachmentMaker' is not defined
The first attachment maker will attach a slur to any consecutive run of notes and/or chords, while the second will attach accents to the first leaf in any runs of consecutive notes and/or chords.
Just as we did before, we initialize an instance of the
MusicMaker() class, passing in the attachment makers as keyword arguments; then, unlike last time, to generate the music, we call the instance itself and pass in the list of time signatures as arguments to generate the music.
In :fast_music_maker = MusicMaker( counts=[1, 1, 1, 1, 1, -1], denominator=16, pitches=[0, 1], attachment_makers=[ slur_attachment_maker, accent_attachment_maker, ], ) music = fast_music_maker([(3, 4), (5, 8), (4, 4)] * 10)
Finally, we create the final score and use it to initialize an instance of the
LilyPondFile() class. This time, we will pass a stylesheet as a keyword argument into the initializer function, which allows us to specify additional layout and typographical details.
In :staff = abjad.Staff([music]) score = abjad.Score([staff]) lilypond_file = abjad.LilyPondFile.new( music=score, includes=['stylesheet.ily'], ) abjad.show(lilypond_file)
In [ ]: