John Harrison CMPS 411 Final Project Music Writer Description: The music writer writes music for three simultaneous voices. Depending on the information you give it, it can write a fugue, a rondo, or any form. As input, it accepts a key, the number of measures the composition should be, the sections, their lengths, names, rhythmic complexities, a list of harmonies, and a list of keys. It outputs a list of notes for each voice. This list is actually a list of beats, and each beat is a list of notes. Each section is given a name, so that it may be transposed in other sections. For the music writer to write descent counterpoint, it must obey all the rules of good voice leading within each voice and between the voices. This is where the logic of Prolog is a natural for this project. A note is choosen randomly from a list of possibilities (a list of notes appropriate for the given harmony and key.) A routine (note_ok) checks to see if the note fits all of the rules of good voice leading. The restrictions are severe --- chances of failure are high. So note_ok fails, and another note as choosen to try from the list of possibilities. Often all of the notes in the list fail the rules of good voice leading, and the program backtracks to rewrite the note before the last note. Sometimes, the program even needs to backtrack to the previous section before being able to write good voice leading for the current section. Here is a typical goal for the music writer: write_piece([time(4,4),measures(9), section(subject,m_b(1,1),alto,unrelated(complexity(1,8),length(8))), section(answer,m_b(3,1),soprano, transposition_of(subject,norm_vcing,norm_rhythm,length(8))), section(cs1,m_b(3,1),alto,unrelated(complexity(3,14),length(8))), section(subjbass,m_b(5,1),bass, transposition_of(subject,norm_vcing,norm_rhythm,length(8))), section(cs11,m_b(5,1),soprano, transposition_of(cs1,norm_vcing,norm_rhythm,length(8))), section(cs2,m_b(5,1),alto, unrelated(complexity(3,14),length(8))), section(ep1a,m_b(7,1),soprano, transposition_of(subject,norm_vcing,norm_rhythm,length(2))), section(ep1b,m_b(7,3),soprano, transposition_of(ep1a,norm_vcing,norm_rhythm,length(2))), section(ep1c,m_b(8,1),soprano, transposition_of(ep1a,norm_vcing,norm_rhythm,length(2))), section(ep1d,m_b(8,3),soprano, transposition_of(ep1a,norm_vcing,norm_rhythm,length(2))), section(ep2a,m_b(7,1),bass, transposition_of(cs1,norm_vcing,norm_rhythm,length(2))), section(ep2b,m_b(7,3),bass, transposition_of(ep2a,norm_vcing,norm_rhythm,length(2))), section(ep2c,m_b(8,1),bass, transposition_of(ep2a,norm_vcing,norm_rhythm,length(2))), section(ep2d,m_b(8,3),bass, transposition_of(ep2a,norm_vcing,norm_rhythm,length(2))), section(acc1,m_b(7,1),alto, unrelated(complexity(1,5),length(8))), section(endsop,m_b(9,1),soprano, unrelated(complexity(1,1),length(1))), section(endalt,m_b(9,1),alto, unrelated(complexity(1,1),length(1))), section(endbass,m_b(9,1),bass, unrelated(complexity(1,1),length(1)))], harmony([[1,6,2,4],[5,6,5,5],[1,6,2,4],[5,6,5,5],[1,6,2,4],[5,6,5,5], [1,1,5,5],[1,1,5,5],[1,1,1,1]]), key([[key(a,natural,minor),m_b(1,1)], [key(e,natural,minor),m_b(3,1)], [key(a,natural,minor),m_b(5,1)], [key(d,natural,minor),m_b(8,1)], [key(a,natural,minor),m_b(9,1)]]), Soprano,Alto,Bass). This input writes a 9 measure piece which could be a start for a fugue. The first list in the input gives the time signature, the number of measures in the piece, and information about each section. First, each section must have a name. This is necessary since other sections may transpose this section. Second, each section must have an entrance place. The first section, the subject for example, enters at measure 1 beat 1. The answer enters at measure 1 beat 3. Then each section has either the functor 'unrelated' meaning write new unrelated materal or 'transposition of' meaning transpose old material. The unrelated functor accepts 2 parameters: rhythmic complexity and length. Rythmic complexity is given a low and high restriction. The lowest number, 1, is a quarter note. The highest, 14, is 4 16ths. The subject can have rhythms from 1 quarter note to an eighth and 2 sixteenths. (The list of possible rhythms is in the 'rhythms' list in the 'constants' section. The are listed in order of complexity i.e. the first rhythm is the rhythms list is a quarter note while the last (the 14th element in the list) is 4 sixteenths.) The transposition_of functor accepts 4 arguments. The first is what section this section should be a transposition of. The second and third how the original section should be altered before they are transposed. Right now, only norm_vcing and norm_rhythm are implemented. They mean, don't distort the melody or change the rhythms. Eventually, the program should be able to augment rhythms and invert, retrograde, etc. melodies. The length parameter determines how long the new section should be. If it is shorter than the old section, the transposition is started at a random place in the old section. (This is appropriate for episodes in fugues etc.) There is a list of harmonies after the structure list. This is just a list of a list of measures. Each measure has a number for each beat, representing the chord that should be used. Then there is a list of keys. Keys can be major or minor, and the second parameter of the key functor can be natural, sharp, or flat. Then there is in entrance place. The example above is input for a piece starting in a minor, then modulating to e minor in measure 3 beat 1, then modulating back to a minor in measure 5 beat 1, etc. So the answer (the second section) is a transposition of the subject transposed from a minor to e minor but using the same harmonic structure. Such input as above takes several hours for an answer. How the program works: The program works in terms of 1/2 steps. An octave is 12 1/2 steps. A major scale is made up of a tonic note, a whole step above it (+2), a whole step above that (+2), a 1/2 step (+1), and 3 more whole steps. This corresponds to the list maj_scale in the constants section: [1,3,5,6,8,10,12]. So the program can determine the notes in a 1 chord of a major scale, for example, by taking the 1st, 3rd, and 5th elements in such a list: 1,5, and 8. A 5 chord would be obtained by taking the 5th, 7th, and 9th of the list. Since there is no ninth element, the program should cycle back to the beginning of the list and take the 2nd element (since the list is 7 elements long and 7+2=9). Such cycling back is the job of get_endless_nth. Notes are represented internally by a pitch corresponding to its number, followed by the length of the note. The length is in terms of 16th notes: a quarter note has length 4 (=4sixteenth notes) while an 8th has length 2 (=2sixteenth notes). The note pitch is an integer representing how many 1/2 steps up a pitch is from the bass pitch c. When the program is writing a new section, first it writes the main notes for the section, then it elaborates these notes based on the rhythmic extremes the users has inputted. Both use the 'generate and test' method of composition. A note is choosen then tested, as explained above. All of the voice leading rules require only the current note and the last note of each voice to determine if the current note is OK, except for the vce_ok_melody rule. This procedure also requires a rule representing what has happened as far as big leaps previously. Big leaps must be filled by step for good voice leading. The procedure always must receive the current rule and update it. When a section is fuly written, the last rule is stored right in the voice. When the writing of the voice is continued, this rule is found, so that sections will be connected cleanly, still following this rule. (The rule is inserted into the voice in the append operation of the write_section routine.) The program has within it an internal list of the sections written before, where they are, what voice they are in, and their scale degrees. This is handled by the w_p_do routine. Tracing the program: The program first gets information about a section. It cuts up each voice to the appropriate place (a beat before the section beginning), gets the rule for the previous section in this voice, separates the chosen voice (the voice the section will be written in) from the unchoosen voices, and goes to write the section. The section is either a new melody or a transposition. New melody composition has already been explain. Transpositions are written as follows: first an offset is made for both the scale degree (based on the differences in harmony) and the actual notes (based on the differences in harmony and the key). Then each note is tried from the old section with these offsets to see if it fits the rules of good voice leading in the new section. If it doesn't, the note can be altered up to 1 full step in either direction (which is done in the generate_possibilities section). Details on individual procedures: The pick_rnd section is the heart of the generation procedure in the generate and test method of picking notes. The strength of this procedure is that it picks randomly from a list, and it keeps reducing the list on backtracking. Therefore, it will never pick the same note twice, and when the list is empty, it fails completely and the program must backtrack farther. The thing I find so appealing about this method is that the program will never write the same piece twice because of the random choice, yet the same option is never selected twice by accident and the routine will fail completely after all option have been tried. Turbo Prolog, Cut, Etc.: I found Turbo Prolog very frustrating and limited, in comparison to the SICTUS Prolog we have used. The necessity to declare all predicates with the types they take I found very limiting --- it made it much more annoying and time consuming to expand basic functions like member, as well as limiting for acceptable input. These types that it allows are also limited. For example, there is no way in Turbo Prolog to make a list such as [1,1,key(c,natural,major),1] since it mixes integers with a functor. Also, because of the limited memory of the machine I am using, I needed to use cut whereever possible. You may consider my program an abuse of the cut predicate, but I didn't have a choice. Also, Turbo Prolog gave me trouble with free variables which I wouldn't have had in SICTUS Prolog.