forked from ology/Music
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ai-genetic-pro
102 lines (85 loc) · 3.1 KB
/
ai-genetic-pro
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
#!/usr/bin/env perl
use strict;
use warnings;
# Evolve a set of pitches where adjacents must be between 2 and 5 half-steps.
# These pitches are then fed into contrapuntal transformations
# and the results are appended to this "melody."
use Data::Dumper::Compact qw(ddc);
use lib map { "$ENV{HOME}/sandbox/$_/lib" } qw(MIDI-Util);
use MIDI::Util qw(setup_score);
use AI::Genetic::Pro ();
use Music::AtonalUtil ();
use Music::Scales qw(get_scale_MIDI);
my @notes = get_scale_MIDI('C', 4, 'major'); # 60, 62...
my $base = 60;
my $ga = AI::Genetic::Pro->new(
-fitness => \&fitness, # fitness function
-terminate => \&terminate, # terminate function
-type => 'combination', # type of chromosomes
-population => 100, # population
-crossover => 0.9, # probab. of crossover
-mutation => 0.05, # probab. of mutation
-parents => 2, # number of parents
-selection => ['Roulette'], # selection strategy
-strategy => ['PMX'], # crossover strategy
-cache => 1, # cache results
-history => 1, # remember best results
-preserve => 1, # remember the bests
-variable_length => 0, # turn variable length OFF
);
$ga->init(\@notes);
$ga->evolve(1000);
# Perform gymnastics with the pitches
my $pitches = $ga->as_array($ga->getFittest);
my $altered = [ map { $_ + $base } @{ alteration( [ map { $_ - $base } @$pitches ] ) } ];
$pitches = [ @$pitches, @$altered ];
print ddc($pitches);
process_midi($pitches);
sub calc {
my ($ar) = @_;
#warn(__PACKAGE__,' ',__LINE__," MARK: @$ar\n");
my $calc = 0;
# Adjacent elements must be within ...
for my $n ( 0 .. @$ar - 2 ) {
my $v = abs( $ar->[$n] - $ar->[$n + 1] );
$calc++ if $v >= 2 && $v <= 5;
}
#warn "\t = $calc\n";
return $calc;
}
#my $i = 0;
sub fitness {
my ($ga, $chromosome) = @_;
# warn(__PACKAGE__,' ',__LINE__," MARK: fitness() ",$i++,"\n");
return calc(scalar $ga->as_array($chromosome));
}
sub terminate {
my ($ga) = @_;
return 1 if $ga->as_value($ga->getFittest) >= 1;
return;
}
sub process_midi {
my ($pitches) = @_;
my $score = setup_score();
for my $p (@$pitches) {
$score->n('qn', $p);
}
$score->write_score($0 . '.mid');
}
sub alteration {
my $pitches = shift;
my $atu = Music::AtonalUtil->new;
# Post mutation, phrase alterations
my $funct = {
invert => sub { my $x = shift; $atu->invert($x, $pitches) },
retrograde => sub { $atu->retrograde(@$pitches) },
rotate => sub { my $x = shift; $atu->rotate($x, $pitches) },
transpose => sub { my $x = shift; $atu->transpose($x, $pitches) },
};
# Choose a random function and execute it with the given alteration factor
my $x = int(rand 3) + 2;
my $alteration = (keys %$funct)[ rand keys %$funct ];
print "Alteration: $alteration by $x\n";
my $altered = $funct->{$alteration}->($x);
return $altered;
}