Guido
Word Music
This is an implementation of one of
the oldest known algorithmic music processes. It is rule based, not
recombinatorial. Each vowel is allocated a pitch. This implementation is
not strict (as the original was designed for Latin texts) but
has been adapter to Roman languages and modern tonal sensibilities. It
is quite intriguing how close this process is the Arvo Pärt's
compositional processes! (Read about it in Hiller's book on Pärt.)
The interface:
This demo is a realisation of
Mozart's design in jMusic by Andrew Troedson.
Click here to view
source part 1.
Click here to view
source part 2.
To hear the result play the
Quicktime movie below.
Andrew Troedson's comments:
An Overview
Based on Guido d'Arezzo's lookup
chart for generating pitches from syllables (ca. 1000 A.D.), this melody
generation tool creates a monophonic phrase by extrapolating
from a given text. A set of rules (see Table 2.1 below) is used
to select note pitches depending on the vowels found in the
text. Although d'Arezzo's original intention was simply to
provide an approximate guide from which a composer could make
selections to fit his/her taste, this tool automates the process, and
itself chooses from the given note options. As well as this, the
wordMusic tool sets the selected notes to a rhythm dependant
on the number of consonants between each vowel (see Table 2.2).
Table 2.1: The note pitch value
options assigned by wordMusic to each vowel
g2
|
a2
|
b2
|
c3
|
d3
|
e3
|
f3
|
g3
|
a3
|
b3
|
c4
|
d4
|
e4
|
f4
|
g4
|
a4
|
A
|
E
|
I
|
O
|
U
|
A
|
E
|
I
|
O
|
U
|
A
|
E
|
I
|
O
|
U
|
A
|
Table 2.2: The rhythm values
assigned by wordMusic dependant of the number of consonants between each
vowel.
<2
|
2-3
|
4
|
>4
|
Quaver
|
Crotchet
|
Minim
|
Dotted Minim
|
Technicalities
This interpretation of d'Arezzo's
lookup chart is set up in a single class, wordMusic.java. However, a GUI
interface which utilizes this class and allows new text to be easily
entered, has also been created and is called
wordMusicMaker.java. The default name of the generated MIDI
file is "wordMusic.mid", although this can be easily changed
when using the GUI interface.
Observations
This melody generation tool, while
based on a set of very simple rules, is quite effective in
creating musically interesting phrases. Because the music it
composes is generated entirely based on this set of rules, it
can be said to be taking an algorithmic approach to the
generation of music.
Initially, the simplicity of the
rules used can be deceiving, however when looked at more closely, they
do have some theoretical grounding. Most melodies that are written for
specific texts do set notes to every syllable, and the rhythm of the
text does in general have some relationship to the length of the words
(and the distance between adjacent vowels). Although this is not an
exact relationship (and problems occur with words that do not
contain vowels, for example "sky"), the results produced by its
implementation are very acceptable. It should also be noted that the
original rhythm algorithm involved a greater variety of note lengths
(ranging from semiquavers to semibrieves), however this was simplified
as the resulting phrases tended to be somewhat disjointed.
Of course, the algorithm used could
be extended further to take voice leading into account, or
could even be developed to produce polyphonic phrases, however,
in its current form, the wordMusic melody generation tool does
provide a good example of the possibilities of algorithmically
generating music - even with only the simplest of rules.
The code:
Let's have a closer look.
/* * A class which creates a GUI interface for * the wordMusic class - allowing for easy * entry of different texts * @author Andrew Troedson */
import java.awt.*; import java.awt.event.*;
public class wordMusicMaker extends Frame implements ActionListener, WindowListener{
TextField fileName; TextArea wordInput; wordMusic musicMaker = new wordMusic();
public void actionPerformed(ActionEvent ae){ if(ae.getActionCommand() == "ActionCompose") { musicMaker.compose(wordInput.getText(), fileName.getText()); } } // Deal with the window closebox public void windowClosing(WindowEvent we) { System.exit(0); } //other WindowListener interface methods //They do nothing but are required to be present public void windowActivated(WindowEvent we) {}; public void windowClosed(WindowEvent we) {}; public void windowDeactivated(WindowEvent we) {}; public void windowIconified(WindowEvent we) {}; public void windowDeiconified(WindowEvent we) {}; public void windowOpened(WindowEvent we) {}; public static void main(String args[]){ wordMusicMaker wmm = new wordMusicMaker("Word Music"); } public wordMusicMaker(String title){ super(title); //register the closebox event this.addWindowListener(this); //assign the frame an icon (only seen in Windows OS) Image i = Toolkit.getDefaultToolkit().getImage("wmicon.gif"); this.setIconImage(i); setLayout(new BorderLayout()); Color lightGrey = new Color(240,240,240); this.setBackground(lightGrey); Label mainTitle = new Label("WORD MUSIC by Andrew Troedson", 1); add(mainTitle, "North");
wordInput = new TextArea("Type text here...", 6, 40, 3); wordInput.setBackground(Color.white); add(wordInput, "Center"); Panel windowBottom = new Panel(); Label outputFile = new Label("Output File:",1); fileName = new TextField("wordMusic.mid",16); Button compose = new Button("Compose"); compose.setBackground(Color.lightGray); compose.setForeground(Color.darkGray); compose.addActionListener(this); compose.setActionCommand("ActionCompose"); windowBottom.add(outputFile); windowBottom.add(fileName); windowBottom.add(compose); add(windowBottom, "South"); Panel eastBorder = new Panel(); add(eastBorder, "East"); Panel westBorder = new Panel(); add(westBorder, "West"); pack(); show(); } }
|
The code is adequately commented such
that you should be able to follow it thorugh. It mainly implements a GUI
which calls the wordMaker class whose code is below.
/* A class which generates music from words * Based on Guido d'Arezzo's lookup chart for generating * pitches from syllabes (ca. 1000 A.D.) * @author Andrew Troedson * * g2 a2 b2 c3 d3 e3 f3 g3 a3 b3 c4 d4 e4 f4 g4 a4 * A E I O U A E I O U A E I O U A */
import java.text.*; //contains the StringCharacterIterator import jm.music.data.*; //the jMusic classes import jm.JMC; import jm.util.*;
public final class wordMusic implements JMC {
//the passage from which the melody will be generated String passage; //the name of the file produced String fileName;
public wordMusic() { } public void setPassage(String passage) { this.passage = passage; } public void setFileName(String fileName) { this.fileName = fileName; } //compose method takes two args: the passage String and the fileName String public void compose(String p, String f) { passage = p; fileName = f; Score score = new Score("wordMusic", 120); Part piano = new Part("Piano", PIANO, 0); Phrase phrase = new Phrase(0.0); Note nextNote = new Note(); //set a default passage if none is given if (passage.length() == 0) { setPassage("twinkle twinkle little star,"+ "how I wonder what you are,"+ " up above the world so high,"+ " like a diamond in the sky."); } //set a default filename if none is given if (fileName.length() == 0) { setFileName("wordMusic.mid"); } //change all the letters in the passage variable to //Upper Case to remove case sensitivity passage = passage.toUpperCase(); //assign a StringCharacterIterator to the passage StringCharacterIterator iter = new StringCharacterIterator(passage);
//the variable used to count the number of non-vowels //(ie consonants and symbols) between each vowel //this number is sent to setNoteLength() every time //a new note is made int nonVowelCounter = 0; //this variable is set each new note is created, and //sets the Pitch of the new note (this is chosen at //random from the 3 or 4 options allowed for that vowel //in d'Arezzo's lookup chart) int notePitch; //the variable used to make the random pitch selection int randNum; //the main iteration loop which goes through passage //and makes the notes these notes are then added to //the Phrase phrase for(int i=0; i<passage.length(); i++) { iter.setIndex(i); char nextChar = iter.current(); System.out.println(i + " " + nextChar); switch (nextChar) { case 'A': randNum = ((int)(java.lang.Math.random()*4)); if (randNum == 0){ notePitch = G2;} else if (randNum == 1){ notePitch = E3;} else if (randNum == 2){ notePitch = C4;} else { notePitch = a4;} nextNote = new Note(notePitch,setNoteLength( nonVowelCounter)); phrase.addNote(nextNote); nonVowelCounter = 0; break; case 'E': randNum = ((int)(java.lang.Math.random()*3)); if (randNum == 0){ notePitch = A2;} else if (randNum == 1){ notePitch = F3;} else { notePitch = D4;} nextNote = new Note(notePitch,setNoteLength( nonVowelCounter)); phrase.addNote(nextNote); nonVowelCounter = 0; break; case 'I': randNum = ((int)(java.lang.Math.random()*3)); if (randNum == 0){ notePitch = B2;} else if (randNum == 1){ notePitch = G3;} else { notePitch = E4;} nextNote = new Note(notePitch,setNoteLength( nonVowelCounter)); phrase.addNote(nextNote); nonVowelCounter = 0; break;
case 'O': randNum = ((int)(java.lang.Math.random()*3)); if (randNum == 0){ notePitch = C3;} else if (randNum == 1){ notePitch = A3;} else { notePitch = F4;} nextNote = new Note(notePitch,setNoteLength( nonVowelCounter)); phrase.addNote(nextNote); nonVowelCounter = 0; break;
case 'U': randNum = ((int)(java.lang.Math.random()*3)); if (randNum == 0){ notePitch = D3;} else if (randNum == 1){ notePitch = B3;} else { notePitch = G4;} nextNote = new Note(notePitch,setNoteLength( nonVowelCounter)); phrase.addNote(nextNote); nonVowelCounter = 0; break; //for all other letters (ie all the consonants) //and syllables, no notes are created default: //add 1 to the consonant counter nonVowelCounter++; break; } } piano.addPhrase(phrase); View.print(piano); //add part (instrument) to the score score.addPart(piano); //OK now we test SMF write and read
Write.midi(score, fileName); }
//an internal method which decides on the length of each note //depending on the number of consonants (as well as spaces //and other symbols) between vowels static double setNoteLength(int counter){ double noteLength; switch (counter) { case (0): noteLength = Q; break; case (1): noteLength = Q; break; case (2): noteLength = C; break; case (3): noteLength = C; break; case (4): noteLength = M; break; default: //(ie more than 4 consonants) noteLength = MD; break; } return noteLength; } }
|
This is the class which does
all the work. The main thing to notice is the switch statement
which checks to see if the nexts letter is a vowel. A switch
statement is also used to select a duration based on the number
of letters between vowels.
|