File Coverage

blib/lib/Music/RhythmSet/Util.pm
Criterion Covered Total %
statement 119 119 100.0
branch 44 44 100.0
condition 30 30 100.0
subroutine 19 19 100.0
pod 13 13 100.0
total 225 225 100.0


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # various functions related to the generation and comparison of patterns
4             # of beats, and etc
5              
6             package Music::RhythmSet::Util;
7             our $VERSION = '0.04';
8              
9 3     3   401916 use 5.24.0;
  3         18  
10 3     3   14 use warnings;
  3         5  
  3         74  
11 3     3   12 use Carp qw(croak);
  3         6  
  3         128  
12 3     3   1186 use Statistics::Lite qw(stddevp);
  3         3940  
  3         169  
13              
14 3     3   18 use constant { NOTE_ON => 1, NOTE_OFF => 0 };
  3         9  
  3         226  
15              
16 3     3   1196 use parent qw(Exporter);
  3         714  
  3         14  
17             our @EXPORT_OK =
18             qw(beatstring compare_onsets duration filter_pattern flatten ocvec onset_count pattern_from rand_onsets score_fourfour score_stddev upsize write_midi);
19              
20             sub beatstring
21             {
22 3     3 1 6009 my ($bpat) = @_;
23 3 100 100     279 croak "no pattern set"
24             unless defined $bpat and ref $bpat eq 'ARRAY';
25 1         9 return join( '', $bpat->@* ) =~ tr/10/x./r;
26             }
27              
28             sub compare_onsets
29             {
30 4     4 1 446 my ( $first, $second ) = @_;
31              
32 4         5 my $same = 0;
33 4         5 my $onsets = 0;
34              
35 4         12 for my $i ( 0 .. $first->$#* ) {
36 6 100       16 if ( $first->[$i] == NOTE_ON ) {
37 5         6 $onsets++;
38 5 100       12 $same++ if $second->[$i] == NOTE_ON;
39             }
40             }
41 4 100       90 croak "no onsets?! [@$first] [@$second]" unless $onsets;
42              
43 3         23 return $same / $onsets;
44             }
45              
46             sub duration
47             {
48 3     3 1 1082 my ($replay) = @_;
49 3 100 100     155 croak "no replay log"
50             unless defined $replay and ref $replay eq 'ARRAY';
51              
52 1         4 my $measures = 0;
53 1         2 my $beats = 0;
54              
55 1         2 for my $ref ( $replay->@* ) {
56 3         5 $measures += $ref->[1];
57 3         7 $beats += $ref->[0]->@* * $ref->[1];
58             }
59              
60 1         5 return $measures, $beats;
61             }
62              
63             sub filter_pattern
64             {
65 2     2 1 23 my ( $onsets, $total, $trials, $fudge, $nozero ) = @_;
66              
67 2   100     9 $fudge //= 0.0039;
68 2         4 my $best = ~0;
69 2         2 my $bpat;
70              
71 2         6 for ( 1 .. $trials ) {
72 11000         14878 my $new = &rand_onsets;
73 11000         15250 my $score = score_stddev($new) + score_fourfour($new) * $fudge;
74 11000 100 100     19642 next if $nozero and $score == 0;
75 10997 100       22817 if ( $score < $best ) {
76 13         19 $best = $score;
77 13         22 $bpat = $new;
78             }
79             }
80              
81 2         33 return $bpat;
82             }
83              
84             sub flatten
85             {
86 3     3 1 701 my ($replay) = @_;
87 3 100 100     151 croak "no replay log"
88             unless defined $replay and ref $replay eq 'ARRAY';
89 1         3 return [ map { ( $_->[0]->@* ) x $_->[1] } $replay->@* ];
  3         17  
90             }
91              
92             # "onset-coordinate vector" notation for a pattern
93             sub ocvec
94             {
95 3     3 1 717 my ($bpat) = @_;
96 3 100 100     152 croak "no pattern set"
97             unless defined $bpat and ref $bpat eq 'ARRAY';
98              
99 1         2 my @set;
100 1         2 my $i = 0;
101              
102 1         3 for my $x ( $bpat->@* ) {
103 12 100       19 push @set, $i if $x == NOTE_ON;
104 12         14 $i++;
105             }
106              
107 1         5 return \@set;
108             }
109              
110             sub onset_count
111             {
112 3     3 1 699 my ($bpat) = @_;
113 3 100 100     187 croak "no pattern set"
114             unless defined $bpat and ref $bpat eq 'ARRAY';
115              
116 1         2 my $onsets = 0;
117              
118 1         2 for my $x ( $bpat->@* ) {
119 12 100       20 $onsets++ if $x == NOTE_ON;
120             }
121              
122 1         7 return $onsets;
123             }
124              
125             sub pattern_from
126             {
127 2     2 1 488 my ($string) = @_;
128 2         6 $string =~ tr/x.//cd;
129 2         4 $string =~ tr/x./10/;
130 2         22 return [ split '', $string ];
131             }
132              
133             sub rand_onsets
134             {
135 11101     11101 1 20459 my ( $onsets, $total ) = @_;
136 11101 100       16447 croak "onsets must be < total" if $onsets >= $total;
137              
138 11100         12116 my @pattern;
139 11100         16234 while ($total) {
140 177000 100       244956 if ( rand() < $onsets / $total ) {
141 44500         53509 push @pattern, NOTE_ON;
142 44500         46531 $onsets--;
143             } else {
144 132500         154943 push @pattern, NOTE_OFF;
145             }
146 177000         235744 $total--;
147             }
148              
149 11100         16018 return \@pattern;
150             }
151              
152             sub score_fourfour
153             {
154 11002     11002 1 733456 my ($bpat) = @_;
155              
156 11002         14761 my @beatquality = map { 256 - $_ } qw(
  176032         197542  
157             256 0 16 4
158             64 0 32 8
159             128 0 16 4
160             64 0 32 8
161             );
162 11002         12804 my $i = 0;
163 11002         11627 my $score = 0;
164              
165 11002         14348 for my $x ( $bpat->@* ) {
166 176016 100       228491 $score += $beatquality[$i] if $x == NOTE_ON;
167 176016         187842 $i++;
168             }
169              
170 11002         20640 return $score;
171             }
172              
173             sub score_stddev
174             {
175 11004     11004 1 15193 my ($bpat) = @_;
176              
177 11004         11356 my @deltas;
178 11004         13646 my $len = $bpat->@*;
179              
180 11004         16751 for my $i ( 0 .. $bpat->$#* ) {
181 176048 100       244511 if ( $bpat->[$i] == NOTE_ON ) {
182 44012         47140 my $j = $i + 1;
183 44012         44786 while (1) {
184 176048 100       239646 if ( $bpat->[ $j % $len ] == NOTE_ON ) {
185 44012         47867 my $d = $j - $i;
186 44012         47027 push @deltas, $d;
187 44012         53637 last;
188             }
189 132036         129533 $j++;
190             }
191             }
192             }
193 11004 100       17842 croak "no onsets?! [@$bpat]" unless @deltas;
194              
195 11003         20942 return stddevp(@deltas);
196             }
197              
198             sub upsize
199             {
200 7     7 1 2188 my ( $bpat, $newlen ) = @_;
201 7 100 100     341 croak "no pattern set"
      100        
202             unless defined $bpat
203             and ref $bpat eq 'ARRAY'
204             and $bpat->@*;
205 3         5 my $len = $bpat->@*;
206 3 100       507 croak "new length must be greater than pattern length" if $newlen <= $len;
207 1         3 my $mul = int( $newlen / $len );
208 1         5 my @pat = (NOTE_OFF) x $newlen;
209 1         4 for my $i ( 0 .. $bpat->$#* ) {
210 4 100       8 if ( $bpat->[$i] == NOTE_ON ) {
211 3         10 $pat[ $i * $mul ] = NOTE_ON;
212             }
213             }
214 1         8 return \@pat;
215             }
216              
217             sub write_midi
218             {
219 7     7 1 6419 my ( $file, $track, %param ) = @_;
220              
221 7   100     35 $param{format} //= 1;
222 7   100     27 $param{ticks} //= 96;
223              
224             MIDI::Opus->new(
225             { format => $param{format},
226             ticks => $param{ticks},
227 7 100       47 tracks => ref $track eq 'ARRAY' ? $track : [$track]
228             }
229             )->write_to_file($file);
230              
231 7         5800 return; # copy "write_to_file" interface
232             }
233              
234             1;
235             __END__