Project: MOOD

 

Automated Rule-based Music Generation

 

 

Ge Wang

 

www.gewang.com

ge.wang@duke.edu

 

 




Design Document and Methods of Implementation

 

 

MOOD is an automated, rule-base, “tactical” music “composer”.  This document discussed the design issues that went into developing MOOD.  It is divided among the following sections.

 

(1)  Purpose and goals

(2)  Premise and inner workings

(3)  Progress in level of intelligence

(4)  Objects, and data Structures.

 

 

purpose and goals

 

 

MOOD is an automated music composition engine.  The input is comprised of sets of specialized rules, which are dynamically added to the framework.  The main processing unit of the MOOD engine traverses each of the components and the rules, evaluates each decision, and ultimately composes the melodies, harmonies, and rhythms.  The capability to compose polyphonic (multiple, interacting voices) is also supported.

 

MOOD is intended not only to serve as a composition engine, but also as a machine that can be extended with user-defined rule sets.  Each rule set, uses the internal MOOD interface and data types to apply its contents (different compositional rules) to the decision-making process.

 

The output of MOOD is independent of the input and represented as the notes of the composition, which can be easily converted to different type of media.  In this case, the MOOD engine outputs to a MIDI / text format, with which the final written notes are readable as pitches and can be listened to using MIDI.

 

 

premise and inner workings

 

 

The MOOD composition engine is based on the idea that music is an highly creative process – and, in many ways, a logical and organized one.  It is also built on the theory that while it is very difficult to construct a creative piece of music, given an vast array of musical possibilities - it is often much easier to determine which of the possibilities are bad, and gradually eliminate as many of them as possible.  This subtractive method is the foundation of MOOD.

 

MOOD is a constraint-based generator.  Initially, it has no rules for composition, and is completely random within the framework of the engine.  Independent rules are then added by component, each of which will try to further narrow down the possibilities of the next segment of composition.  Ultimately, when there no more criteria upon which to eliminate any more possibilities (notes, harmonies, and rhythms), the engine will make a randomized decision among the choices that remain.  The goal, in this case, is to reduce each decision to a minimum set of choices.

 

The components of MOOD are divided between the composers, which will apply rules to an increasingly constrained set of choices in a careful order, and the supporting data structures with which one is able to represent the rules and structure within the music.

 

In MOOD, a composer is an organized set of rules that are applied in a predetermined order to the composition.  The composition, represented as a composition stream, is passed from composer to composer, each of which will try to further narrow down the choices for the next decision.  At the end of each round, the terminal composers (Melody, Bass, and Voice composers) use the choices that still remain to determine the final outcome of the next segment of music.  The various types of composers in MOOD are as follows.

 

Tone Composer – this composer lays down the fundamental harmonic shape and progression, through the use of note masks and harmony masks (see section 4).  It contains the rules and constraints that dictate the overall tonal shape of the composition.

 

Melody Shape Composer – This builds on the results of the Tone Composer to outline a melodic shape, recording important melodic movements.  Notes outlined in this module will very likely be used as part of the final composition.

 

Bass Shape Composer – This is similar to the Melody Shape Composer, but composes a bass line.  Rules of this module are able to use the knowledge gained from the Tone Composer and the Melody Shape Composer (or vice versa).  This allows the composition to examine and follow the harmonic movement of the piece.

 

Rating Composer – This composer analyzes the tonal progression constructed so far from the Tone, Melody, and Bass Shape Composers, and assign ratings to pitches that could be used as melody.  A vector of ratings accompanies each harmonic change.  Each element in vector represents the attractiveness of a pitch to harmonic movement and the melodic shapes.  This is a very important step, as the ratings will be the final determining factors for the placement of each note.  While the rating Composer is not the only composer that can assign or modify the pitch rating, it will most likely do most of the evaluation.  For example, a rating composer could enforce a tendency to follow stepwise melodic movement.

 

Motive Composer – This composer will examine the melodic and bass shapes and add embellishments to the melodic line.  The melody and bass composer will eventually fill in the notes that are empty.  This is the place to really liven up the composition rhythmically.

 

Melody Composer – Melody composer is the final place to add constraints to the composition stream.  At the end, the composer is responsible for choosing from the remaining choices of notes.

 

Bass Composer – Like the Melody Composer, the Bass Composer allow different part-writing rules to be applied to the bass line.  Similarly, the bass composer will make the decision on all notes in the bass, from the remaining choices.

 


progress in levels of intelligence

 

 

The section documents the progress of the MOOD engine and introduces some algorithms used by the engine during the composition process.  The first musical piece generated is almost completely tonally random.  It follows this algorithm:

 

      out.newTrack( 3, 40 );

 

      for (int i = 0; i < numStuff; i++)

      {

            for (int j = 0; j < 9; j++)

            {

                  out.addNote (

                        ourStrVector

                        [randInt(NUM_PITCH_CLASSES)],

                        randInt(2, 4), 1

                  );

                  out.advanceBeat();

            }

      }

 

      out.endTrack();

 

Here is the resulting piece:

 


rand1.mid

 

Next, I created a pitch mask, with which I filtered out notes that would be dissonant by eliminating notes that are not in the key of ‘C’.

 

          NoteMask mask = new NoteMask(false);

 

          mask.setTrue(C_NATURAL);

          mask.setTrue(D_NATURAL);

          mask.setTrue(E_NATURAL);

          mask.setTrue(F_NATURAL);

          mask.setTrue(G_NATURAL);

          mask.setTrue(A_NATURAL);

          mask.setTrue(B_NATURAL);

 

          for (int i = 0; i < 40; i++)

          {

                for (int j = 0; j < 8; j++)

                 {

                        out.addNote (

                              ourStrVector[getInt(mask)],

                             randInt(2, 4), 1

                        );

                        out.advanceHalfBeat();

                 }

          }

 

Here is the result:

 


rand2.mid

 

By filtering out the notes that doesn’t ‘belong’ in the key, we end up with a less random piece of composition.

 

Next, we restrain the notes to be chosen from a varying set of ‘chords’.  Each chord in the series of chords is randomly generated through a mask, and then the mask is applied to the random note generation.  We introduce a chord mask to filter out chords that we don’t want.  At this point, the quality of each chord (major, minor, diminished, augmented, major 7th, minor 7th, diminished 7th, dominant 7th, and half diminished) are not dependent on the root of the chord chosen, thus this model still leaves room open for notes that are in different keys.

 

          NoteMask mask = new NoteMask (true);

          NoteMask chMask = new NoteMask (false);

          chMask.setTrue (C_NATURAL);

          chMask.setTrue (D_NATURAL);

          chMask.setTrue (E_NATURAL);

          chMask.setTrue (G_NATURAL);

          chMask.setTrue (A_NATURAL);

 

          for (int i = 0; i < 40; i++)

          {

                   chordMask(mask, getInt(chMask), randInt(9));

                    for (int j = 0; j < 8; j++)

                    {

                         out.addNote( ourStrVector[getInt(mask)],

                                       randInt(2,4), 1 );

                          out.advanceHalfBeat();

                    }

          }

 

Here is the result:

 


rand3.mid

 

Though now you may be able to make out the harmonic structure of each segment, the order of the chords are in not particular structure.  So now we try the cycle of fifth for the root of each chord in the progression. Additionally, we limit each chord to have MAJOR quality.

 

 

          int chordRoot = C_NATURAL;

 

          for (int i = 0; i < 40; i++)

          {

                   // cycle of 5th

                   chordMask(mask, chordRoot =

                                 (chordRoot + 7) % NUM_PITCH_CLASSES_ABS, MAJOR );

 

                   for (int j = 0; j < 8; j++)

                   {

                             out.addNote(

                                 ourStrVector[getInt(mask)],

                                 randInt(2,4), 1

                             );

                             out.advanceHalfBeat();

                   }

          }

 

Here is the result:


rand4.mid

 

Example of MOOD – OS Engine Compositions

 

The following are results from an actual rule set loaded into the MOOD engine and composed using its infrastructure.  It uses three rules (one each for ToneComposer, MelodyComposer, BassComposer)

 

          public void process(MoodCompStream stream)

          {

                   int loop = 0;

                   MaskLine mline = stream.toneLine();

                   QualLine qline = stream.quality();

                   int register = 4;

 

                   Pitch prevPitch = null;

                   Note note;

                   Pitch currPitch = new Pitch(stream.tonic, register);

 

                   qline.first();

                   for(mline.first(); !mline.isDone(); mline.next())

                   {

                             RateMask newMask =

                                      RegisterMask (

                                                qline.currentQual().rateMask,

                                                register,

                                                currPitch

                                                );

 

                             int pitchVal = newMask.getRandHighest();

 

                             if (pitchVal != MooMask.INVALID_VALUE)

                             {

                                      currPitch = new Pitch(pitchVal);

                                      note = new Note(currPitch);

 

                                      stream.melody().addNewNode (

                                                note,

                                                mline.currentTime()

                                      );

                             }

 

                             qline.next();

                   }

 

Here are the results:

 

objects and data structures

 

 

Representation for Music Objects:

 

          PitchClass

          Pitch

          Measure

          Note

          Chord

          Interval

          Key

 

MOOD engine classes

 

Composition Modules

 

          Composer (base class)

          ToneComposer

          MelodyShapeComposer

          BassShapeComposer

          MotiveComposer

          RatingComposer

          MelodyComposer

          BassComposer

 

Composition Data Structures

 

Masks

          MooMask

          PitchClassMask

          PitchMask

          RateMask

 

MoodCompStream

TimeLine

NoteLine

QualLine

MaskLine

 

MIDI output

 

                   MidiEvent

                   MidiFileOutStream

                   MidiHeader

                   MidiTextWriter

                   MidiTimeRep

                   MidiWriter

                   NotePacket

                   PrintWriterPlus