| [ index | links | mail ] |
::perl |
Automatic Music with PerlCombining a good idea, a couple of modules from CPAN and Perl's versatility, it is possible to create music automa[gt]ically.ProjectOriginal 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 proceedEverything 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.
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 ];
}
}
ResultsThis 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 messageHere some examples of what can be done with this program:
ImprovementsAs 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
|
Last modified: 09:37:37 25-Dec-2001 / Maintained by larsen