Automated Mozart, by larsen

TL;DR

From the past… generative music with no brains!

About 15 years ago I had the pleasure and honor to know larsen.

He used Markov chains to generate music automatically, starting from an example MIDI file. Alas, after some time his Automatic Music with Perl became a bit stale and the pointer to the code was lost…

Internet Archive to the rescue! Here’s a copy of the code, for future generations:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
#!/usr/bin/perl

use MIDI;
use MIDI::Simple;
use Getopt::Mixed;
#use Tie::Hash::Cannabinol :))
use strict;

use vars qw/
    $opt_f
    $opt_o
    $opt_t
    $opt_s
    $opt_c
    $opt_n
    $opt_h
/;

Getopt::Mixed::getOptions(
    'f=s filename>f o=s output>o t=i track>t s=i tuple_size>s c=i tracks_to_be_composed>c n=i notes>n h help>h');

my $filename = $opt_f || (usage() and die);   # a MIDI file to learn from
my $output_file = $opt_o || 'fake.mid';
my $track_number = $opt_t || 6;               # the track from $filename to be learned            
my $tuple_size = $opt_s || 9;                 # how many notes for each tuple?
my $tracks_to_be_composed = $opt_c || 3;      # how many tracks?
my $notes_per_track = $opt_n || 100;          # how many notes must be composed per track?

my %tuples = ();

usage() and exit if $opt_h;

my $song = MIDI::Opus->new(
    { from_file => $filename }
) || die "Problems opening MIDI file...\n";


info( $song );
my $score = get_track( $song, $track_number );
learn( $score );
compose();

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 );
}


sub learn
{
    my $score = shift;

    my @notes = map { $_->[4] } @$score;
    
    while( @notes ) {
        if( scalar @notes >= $tuple_size + 1 ) {
            push @{ $tuples{ join ' ', @notes[0 .. $tuple_size-1] } }, 
                $notes[$tuple_size];
        }
        shift @notes;
    }
    
    print "I've learnt ", scalar keys %tuples, " tuples...\n";
    my $sum = 0;
    $sum += scalar @{ $tuples{$_} } for keys %tuples;
    print "Average number of buckets per key: ", 
        ($sum / scalar keys %tuples), "...\n";
}


sub compose
{
    new_score();

    my $t = 1;

    while( $t <= $tracks_to_be_composed ) {
        print "Composing track $t...\n";    
        compose_track( $t );
        $t++;
    }
    
    print "Finished.\n";
    write_score( $output_file );
}


sub compose_track
{
    my $n = $notes_per_track;
    my ($hit, $miss);

    my @tempo = qw/wn hn qn en sn/;
    my @volume = qw/ppp pp p mp m mf f ff fff/;
    
    my $l = scalar keys %tuples;
    my $k = ( keys %tuples )[ int rand $l ];

    Time( 1 );
    Channel( shift );
    while( $n-- ) {
        my @tuple = @{ $tuples{ $k } };
        my $last = $tuple[ int rand scalar @tuple ];

        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;
            ++$hit;
        }
        else {
            $k = ( keys %tuples )[ int rand $l ];
            ++$miss;
        }
    }
    print "\tHit/Miss ratio: $hit/$miss\n";
}


sub info
{
    my $song = shift;

    print "Fetching info from MIDI file...\n";
    printf "%6s %-6s %-20s %-20s\n", '#', 'Type', 'Track Name', 'Instrument';
    my $counter = 0;
    foreach my $t ( $song->tracks() ) {
        my $track_info = '';
        my %info = ();
        my $flag = 0;

        foreach($t->events()) {
            if ($_->[0] eq 'track_name' || $_->[0] eq 'instrument_name') {
                $info{ $_->[0] } = $_->[2];
                $flag++;
            }
            last if $flag == 2;
        }

        printf "%6d %-6s %-20s %-20s\n", 
            ++$counter,
            $t->type(),
            $info{'track_name'},
            $info{'instrument_name'};
    }
}


sub usage
{
    print <<USAGE;
    
    mozart.pl -f peaches.mid -t 1 -s 6 -n 100
    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

USAGE
}

Local copy.

Thanks larsen 😄


Comments? Octodon, , GitHub, Reddit, or drop me a line!