News » 23 April 2008

Musical Grammar Evolution In Max Via Ruby

Now that I have a pretty stable environment for running Ruby inside Max/MSP, I'm trying to figure out what to actually do with it.

I've been wanting to learn more about generative/evolutionary algorithms, and Ruby seems like a great language for exploring these concepts in a semi-interactive way. I stumbled across this Ruby Gem called DRP the other day: http://drp.rubyforge.org/

Note: the code and patches require the latest ajm objects

I thought this could be an interesting way to generate music, so I decided to try this Grammatical Evolution (GA) approach for generating input to my sequencing objects. I designed the syntax for my sequencing objects, so I know the grammar inside and out. It was pretty easy to formalize it into something useable by GA (note: this grammar is not comprehensive):

<seq>           ::= <subseq> <whitespace> <sequence> | <subseq>
<subseq>        ::= <repetition> | <chord> | <items>
<repetition>    ::= "(" <seq> ")*" <digits>
<chord>         ::= "[" <items> "]" | "[" "]"
<items>         ::= <item> <whitespace> <items> | <item>
<item>          ::= <note> | <number>
<note>          ::= <note-name> <octave> | <note-name> <accidentals> <octave>
<note-name>     ::= "C" | "D" | "E" | "F" | "G" | "A" | "B"
<accidentals>   ::= <accidental> | <accidental> <accidentals>
<accidental>    ::= "#" | "b" | "+" | "_"
<octave>        ::= "-1" | digit
<number>        ::= <signed-digits> | <signed-digits> "." <digits>
<signed-digits> ::= "-" <digits> | <digits>
<digits>        ::= <digit> <digits> | <digit>
<digit>         ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<whitespace>    ::= " "

So on to the coding. Installing DRP was pretty straightforward, just needed to use the JRuby gem command instead of my computer's built-in gem command:

$ export PATH=/Users/adam/workspace/jruby-1.1.1/bin:$PATH
$ gem install drp

Then I copied the lib/gems directory over to my Max java lib directory where I have my ajm objects jar file installed.

As an initial test I tried out this simple DRP program using a subset of the grammar from above:

require 'rubygems'
require 'drp'

class NoteGen

  extend DRP::RuleEngine

  begin_rules #####################

  def subseq; "#{note}" end

  def note; "#{notename}#{octave}" end
  def note; "#{notename}#{accidental}#{octave}" end

  def notename; "C" end
  def notename; "D" end
  def notename; "E" end
  def notename; "F" end
  def notename; "G" end
  def notename; "A" end
  def notename; "B" end

  def accidental; "#" end
  def accidental; "b" end

  def octave; "-1" end
  def octave; "0" end
  def octave; "2" end
  def octave; "3" end
  def octave; "4" end
  def octave; "5" end
  def octave; "6" end
  def octave; "7" end
  def octave; "8" end
  def octave; "9" end

  max_depth 20
    weight 10
      def seq; "#{subseq} #{seq}" end
    weight 1
      def seq; "#{subseq}" end

  end_rules #########################

  def default_rule_method; "" end

end

notegen = NoteGen.new

Once I run that code, I can call notegen.seq repeatedly to get strings like these:

B5 F9 B#5 Gb4 G2 Fb3 B2 F-1 G3 E#0 F#6 F#7 F3 D7
Db5 D#3 Bb5
G6 G0 Ab2 A6 C4 Ab8 B#-1 Ab6 G6 G0 A3 G8
Eb8 B#8

It's basically just random notes, but I figured, what the hell, let's make a Max 5 patch out of it. I kept it really simple:

This just runs two "notegen" sequences in parallel. I wasn't trying to hard, didn't even vary the rhythm or anything. Just wanted to see what happens. I had minimal control over what was going on - basically I was just a monkey pressing a button whenever I wanted the music to change. The computer did the rest.

I sent the resulting MIDI stream over to Logic. To spice it up a bit I sent it to a sampler that has different sounds assigned to different notes. The lower notes trigger those percussive sounds of tapping a guitar.

Finally, here's the result (1:46):

So, that's kind of... well I'm going to say, bad, but I've been to concerts that are worse ;) This was to be expected because I didn't teach the computer anything besides the grammar. And really, it did a fine job of generating sequences from the grammar. The next step would be to try to encode some musical knowledge in the generation algorithm. Teach the computer something about voice leading and a/tonality and the results should get much better.

As far as the evolutionary algorithm side of this, I need some kind of fitness function to determine the musical fitness of these sequences. Then, from fit sequences I could generate offspring and mutations, leading to developmental material for forming a larger scale piece. I like the idea of letting the human user serve as the fitness function: the computer generates and plays a musical fragment, and I decide if I like it or not. To extend this idea further, a neural network could be trained on the user's tastes and accelerate the evolution of the musical material without requiring constant human input.

Definitely some neat ideas, but I think it'll be a few months or even years before I explore them deeply enough to get anywhere interesting. In the meantime I have a the beginnings of a new toolkit for composing with computers.

Some more evolutionary/AI related Ruby projects:
http://charlie.rubyforge.org/
http://ai4r.rubyforge.org/