In :import abjad import abjadext.rmakers %reload_ext abjadext.ipython
Almost all complex projects created with Abjad dependn on a custom set of music-generating classes, and its essential to understand object-oriented programming to begin scaling your ideas into complex musical projects.
Procedural code views the world as a collection of verbs (actions): procedural code passes data into functions and functions pass altered data out to other functions, until the desired result has been achieved.
Object-oriented programming, on the other hand, views the world as a collection of nouns (objects) that act in an environment. The objects have both characteristics (attributes) and characteristic behaviors (methods).
In this notebook, we're going to take our procedural code from the previous notebook and turn our functions into the characteristic behaviors (methods) of a music-generating class.
Now we'll define a
MusicMaker() class. When we create an instance of this class by calling the class's initializer (
my_musicmaker = MusicMaker(counts, denoms, pitches)), the class instance will be able to do the four characteristic behaviors we've given it (make basic rhythms, chop at meter, paint on pitches, and add compositional detail, not to mention use these all together in a
In :class MusicMaker(object): def __init__( self, counts, denominator, pitches, clef, ): self.counts = counts self.denominator = denominator self.pitches = pitches self.clef = clef def make_basic_rhythm(self, time_signature_pairs, counts, denominator, clef): # THIS IS HOW WE MAKE THE BASIC RHYTHM total_duration = sum(abjad.Duration(pair) for pair in time_signature_pairs) talea = abjadext.rmakers.Talea(counts=counts, denominator=denominator) talea_index = 0 all_leaves =  # create an empty list for generated leaves current_duration = abjad.Duration(0) # keep track of the total duration as we generate each new leaf while current_duration < total_duration: # generate leaves until they add up to the total duration leaf_duration = talea[talea_index] # get a fraction from the talea if leaf_duration > 0: pitch = abjad.NamedPitch("c'") # assign the leaf a pitch of middle C else: pitch = None # if the leaf is a rest, don't assign a pitch leaf_duration = abs(leaf_duration) # cancel the minus sign on the duration if (leaf_duration + current_duration) > total_duration: leaf_duration = total_duration - current_duration # catch any end condition by truncating the last duration current_leaves = abjad.LeafMaker()([pitch], [leaf_duration]) # make the leaves all_leaves.extend(current_leaves) # add the new leaves to the list of leaves current_duration += leaf_duration # advance the total duration talea_index += 1 # advance the talea index to the next fraction music = abjad.Container(all_leaves) abjad.attach(abjad.Clef(clef), music) return music def clean_up_rhythm(self, music, time_signature_pairs): # THIS IS HOW WE CLEAN UP THE RHYTHM time_signatures =  for item in time_signature_pairs: time_signatures.append(abjad.TimeSignature(item)) splits = abjad.mutate(music[:]).split(time_signature_pairs, cyclic=True) for time, measure in zip(time_signatures, splits): abjad.attach(time, measure) selector = abjad.select(music).leaves() measures = selector.group_by_measure() for time, measure in zip(time_signatures, measures): abjad.mutate(measure).rewrite_meter(time) return music def add_pitches(self, music, pitches): # THIS IS HOW WE ADD PITCHES pitches = abjad.CyclicTuple(pitches) logical_ties = abjad.iterate(music).logical_ties(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): # THIS IS HOW WE ADD DYNAMICS AND ACCENTS for run in abjad.select(music).runs(): abjad.attach(abjad.Articulation('accent'), run) if 1 < len(run): abjad.hairpin('p < f', run) abjad.override(run).dynamic_line_spanner.staff_padding = 3 else: abjad.attach(abjad.Dynamic('ppp'), run) return music def make_music(self, time_signature_pairs): music = self.make_basic_rhythm( time_signature_pairs, self.counts, self.denominator, self.clef ) music = self.clean_up_rhythm(music, time_signature_pairs) music = self.add_pitches(music, self.pitches) music = self.add_attachments(music) return music
We can now create an instance of our
MusicMaker() class by calling our class initializer. Note that we need to pass in counts, denominators, and pitches to the initializer function, as specified in the
__init__ method of our class definition.
In :# THIS IS THE INPUT TO MY MUSICAL IDEA time_signature_pairs = [(3, 4), (5, 16), (3, 8), (4, 4)] counts = [1, 2, -3, 4] denominator = 16 pitches = abjad.CyclicTuple([0, 3, 7, 12, 7, 3]) clef = "treble"
In :my_musicmaker = MusicMaker(counts, denominator, pitches, clef)
Finally, we can call our maker's
make_music method to generate music. Note that we need to pass in our time signature pairs, as we specified in the class definition:
In :music = my_musicmaker.make_music(time_signature_pairs) abjad.show(music)
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 pass different sets of initializers into a few different instances of our class.
In :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=, clef="treble" ) sparkling_music_maker = MusicMaker( counts=[1, -5, 1, -9, 1, -5], denominator=16, pitches=[38, 39, 40], clef='treble^8', )
Let's use these four musicmakers 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:
In :upper_staff = abjad.Staff() lower_staff = abjad.Staff() time_signature_pairs = [(3, 4), (5, 16), (3, 8), (4, 4)]
Next, we iterate through a list of makers, allowing each to generate music that adds up to and fits inside the succession of time signatures, and append the music to our staves. We'll generate music for the top and bottom staff independently, but it's possible to create a musicmaker that outputs music for multiple staves simultaneously (as, for example, a list of selections, one per staff).
In :for music_maker in ( # in itereation, each differently configured music maker gets assigned to the name "music_maker" fast_music_maker, slow_music_maker, stuttering_music_maker, sparkling_music_maker, ): music = music_maker.make_music(time_signature_pairs) # the current music_maker generates music upper_staff.append(music) # add the generated music to the staff for music_maker in ( # repeat above for lower staff slow_music_maker, slow_music_maker, stuttering_music_maker, fast_music_maker, ): music = music_maker.make_music(time_signature_pairs) lower_staff.append(music)
In :piano_staff = abjad.StaffGroup() piano_staff.extend([upper_staff, lower_staff]) score = abjad.Score([piano_staff]) score.add_final_bar_line() abjad.show(score)
And we can also play our score back: