#!/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 <