[ index | links | mail ]

Automatic Music with Perl

Combining a good idea, a couple of modules from CPAN and Perl's versatility, it is possible to create music automa[gt]ically.

Project

Original idea, as long as much of the code, is by Briac Pilprè. Briac took inspiration from a program written by Andrew Plotkin (check references at the end of this document).

In general terms, it's possible to create new token sequences "on footprints" of an existing one. In Briac's original program, tokens were words. In my program, tokens are notes. Given a sequence of notes, i.e. a melodic line, we can create a new sequence, that takes inspiration from the original one. In the following article I'll try to explain in what sense I used the word "inspiration". Together with Briac's important contribute, the module gerarchy MIDI::*, available on CPAN, written by Sean Burke, was fundamental.

How to proceed

Everything begins taking a melodic line from a MIDI file.

sub get_track
{
	my ($song, $track_number) = @_;
	
	print "Fetching track $track_number...\n";
	my $track = @{ $song->tracks_r() }[ $track_number - 1 ];
	
	return MIDI::Score::events_r_to_score_r( $track->events_r );
}

Then, sliding a "window" of N+1 tokens width, the program individuates at every cycle a key-value couple that has to be inserted in the hash table. The key is generated joining the first N elements. The last element is the value, that is inserted in the list associated to the key.

Of course, collisions are possible. Actually, collisions are "the salt" of the algorithm: without them, this program could play (almost) only the given melody. Here the snippet that "learns" from the initial melody. In the code, the parameter N is called $tuple_size.

while( @notes ) {
	if( scalar @notes >= $tuple_size + 1 ) {
		push @{ $tuples{ join ' ', @notes[0 .. $tuple_size-1] } }, 
			$notes[$tuple_size];
	}
	shift @notes; # now the window slides
}

Here an example of the algorithm at work. I added to the normal output a (partial) list of the couples key-value generated by the program.

Fetching track 6...
I've learnt 376 tuples...
Average number of buckets per key: 1.90159574468085...
 1. '38 41 29 34' => 35
 2. '43 35 35 42' => 47
 3. '41 42 43 46' => 38 38
 4. '40 35 42 47' => 42 42 42 42 42 42
 5. '46 38 39 40' => 41
 6. '40 42 40 40' => 35 47 35 35 35 35 35
 7. '42 40 37 36' => 40 40 40 40 40 40 40 40
 8. '54 52 49 42' => 49
 9. '42 47 42 35' => 35 35 35 35 35 35 35 35
10. '40 42 40 43' => 43
11. '46 33 33 33' => 45
12. '45 51 52 45' => 44
13. '48 40 40 44' => 32
14. '53 41 41 41' => 43
...

The program is learning from "Peaches en Regalia" by Frank Zappa. N = 4. Melodic line is taken from the bass part (in this MIDI file, it's the 6th track). You can see that some sequences of notes appear only once in the melody, like '40 42 40 43 43' (line 10). Others, on the other end, are very frequent: '42 47 42 35 35' (line 9). Some musical phrases end in a way more often than in another one: for example, it is much more probable that after the sequence '40 42 40 40' a '35' is played, rather than a '47'. You could figure these lists as "bags" from which the program draws a number every time it encounters a sequence of N notes, a potential key. Why "potential"? It is possible that a sequence of notes generated by the program has never been encountered during the learning phase. This is the case when N is not a multiple of the length of the initial sequence.

Seeing the data structure, it is easy to guess how the composition phase works. Randomly taken a key, the program plays a note randomly chosen from the corresponding list. This note is queued to the notes that compose the key. In orded to have a new good key of size N, the first note is drop. This new key is used for lookup in the next cycle. If the new key is not defined in the hash, another one is chosen, randomly.
And so on, until every note asked by the user has not been played...

while( $n-- ) {
	# $k has been randomly chosen before entering in this cycle 
	my @tuple = @{ $tuples{ $k } };
	my $last = $tuple[ int rand scalar @tuple ];

	# n() is defined by MIDI::Simple
	n( $last, $tempo[ rand scalar @tempo ], 'f' );

	my @next_tuple = split / /, $k;
	shift @next_tuple;
	push @next_tuple, $last;

	if (defined $tuples{ join ' ', @next_tuple }) {
		$k = join ' ', @next_tuple;
	}
	else {
		$k = ( keys %tuples )[ int rand $l ];
	}
}

Results

This simple algorithm (with randomly-chosen tempo, and fixed dynamics) produces results that are not always satisfying from an aestethic point of view, but always encouraging. Here you can find the code.

	mozart.pl 
	by Stefano Rodighiero - larsen@perlmonk.org

	-f --filename                 A midi file to learn from
	-t --track                    What track has to be learned?
	-s --size                     Number of notes for tuple
	-c --tracks_to_be_composed
	-n --notes                    Notes to be composed

	-h --help                     Shows this message
Here some examples of what can be done with this program:

Peaches en Regalia
by Frank Zappa
Example 1
Example 2
Frame by Frame
by King Crimson
Example 1
Example 2
Example 3
Epitaph
by King Crimson
Example 1
Example 2
Example 3

Improvements

As I said above, only the melodic line is produced with the tecnique I just described. Tempo and dynamic informations are suitable to be learned and composed in the same manner. This can be done easily encapsulating the algorithm in a module or using something like Algorithm::MarkovChain.

References

Transcramble - Random text generator
The "good idea" I talk about in the subtitle. You'll find also my reply to Briac's post, where I propose to apply the same technique to music.
Fun with Markov Chains
The original page explaining the program written by Andrew Plotkin
Perl and MIDI: Simple Languages, Easy Music
Very interesting article about MIDI modules and music written by their author, Sean Burke. Check also his homepage.

Last modified: 09:37:37 25-Dec-2001 / Maintained by larsen