File Coverage

blib/lib/BokkaKumiai.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package BokkaKumiai;
2 1     1   1983 use Mouse;
  0            
  0            
3             use Mouse::Util::TypeConstraints;
4             our $VERSION = '0.02';
5              
6             #- type
7             subtype 'BokkaKumiai::Keys'
8             => as 'Str',
9             => where { $_ =~ /^(C|C#|Db|D|D#|Eb|E|F|F#|Gb|G|G#|Ab|A|A#|Bb|B)$/ }
10             => message { "This key ($_) is not musical keys!" }
11             ;
12             subtype 'BokkaKumiai::Time'
13             => as 'Str',
14             => where { $_ =~ /^\d+\/\d+$/ }
15             => message { "This time ($_) is not musical time!" }
16             ;
17             subtype 'BokkaKumiai::Beat'
18             => as 'Int',
19             => where { $_ =~ /^(2|4|8|16)$/ },
20             => message { "This beat ($_) is not musical beat!" }
21             ;
22             subtype 'BokkaKumiai::Tension'
23             => as 'Int',
24             => where { $_ =~ /^(undef|0|1|2|3|4)$/ }
25             => message { "This tention level ($_) is not supperted by BokkaKumiai.enter 1-4" }
26             ;
27             subtype 'BokkaKumiai::OneRow'
28             => as 'Int',
29             => where { $_ =~ /^(2|4)$/ }
30             => message { "This bars_by_one_row ($_) is not supperted by BokkaKumiai: enter 2 or 4" }
31             ;
32              
33             #- input
34             has 'key' => (
35             is => 'rw',
36             isa => 'BokkaKumiai::Keys',
37             required => 1,
38             );
39             has 'time' => (
40             is => 'rw',
41             isa => 'BokkaKumiai::Time',
42             required => 1,
43             default => '4/4',
44             );
45              
46             has 'beat' => (
47             is => 'rw',
48             isa => 'BokkaKumiai::Beat',
49             default => 4,
50             required => 1,
51             );
52             has 'pattern' => (
53             is => 'rw',
54             isa => 'Str',
55             required => 1,
56             );
57              
58             has 'chord_progress' => ( #- コード進行
59             is => 'rw',
60             isa => 'ArrayRef',
61             );
62              
63             has 'tension' => (
64             is => 'rw',
65             isa => 'BokkaKumiai::Tension',
66             );
67              
68             has 'bars_by_one_row' => ( #- 一行の小節数(タブ)
69             is => 'rw',
70             isa => 'BokkaKumiai::OneRow',
71             default => 2,
72             );
73             __PACKAGE__->meta->make_immutable;
74             no Mouse;
75             no Mouse::Util::TypeConstraints;
76              
77             use Data::Dumper;
78              
79             #- customize your favorite chords
80             #- if undefined, substituted by auto calculated chords.
81             my $guitar_chords = +{
82             'standard' => +{
83             'C' => [qw(0 1 0 2 3 X)],
84             'Cm' =>[qw(3 4 5 5 3 3)],
85             'C6'=> [qw(0 1 2 2 3 X)],
86             'C69'=>[qw(0 3 2 2 3 X)],
87             'CM7'=>[qw(0 0 0 2 3 X)],
88             'C7' =>[qw(0 1 3 2 3 X)],
89             'C#' =>[qw(4 6 6 6 4 4)],
90             'C#M7'=>[qw(4 6 5 6 4 4)],
91             'D' => [qw(2 3 2 0 0 X)],
92             'D7'=> [qw(2 1 2 0 0 X)],
93             'Dm'=> [qw(1 3 2 0 0 X)],
94             'Dm7'=>[qw(1 1 2 0 0 X)],
95             'Eb'=> [qw(6 8 8 8 6 6)],
96             'Eb7'=>[qw(6 8 6 8 6 6)],
97             'E'=> [qw(0 0 1 2 2 0)],
98             'E7'=> [qw(0 0 1 0 2 0)],
99             'Em'=> [qw(0 0 0 2 2 0)],
100             'Em7'=>[qw(0 0 0 0 2 0)],
101             'F' => [qw(1 1 2 3 3 1)],
102             'Fm'=> [qw(1 1 1 3 3 1)],
103             'FM7'=>[qw(0 1 2 3 3 X)],
104             'FM79'=>[qw(0 1 0 3 3 X)],
105             'G' => [qw(3 0 0 0 2 3)],
106             'Gm'=> [qw(3 3 3 5 5 3)],
107             'G7'=> [qw(1 0 0 0 2 3)],
108             'Ab'=> [qw(4 4 5 6 6 4)],
109             'Ab6'=>[qw(X 6 5 6 6 X)],
110             'Ab7'=>[qw(4 4 5 4 6 4)],
111             'Am'=> [qw(0 1 2 2 0 0)],
112             'Am7'=>[qw(0 1 0 2 0 0)],
113             'Bb'=> [qw(1 3 3 3 1 1)],
114             'Bbm'=>[qw(1 2 3 3 1 1)],
115             'Bb7'=>[qw(1 3 1 3 1 1)],
116             'Bbm7'=>[qw(1 2 1 3 1 1)],
117             'B'=> [qw(2 4 4 4 2 2)],
118             'Bm'=> [qw(2 3 4 4 2 2)],
119             },
120             'funky' => +{
121             #- now developing..
122             },
123             };
124              
125             #- サブルーチン群
126             #- コード進行出力
127             sub print_chord_progress {
128             my ( $self ) = shift;
129             my ( $output ) = "Time: $self->{time}\n";
130             $output .= "Beat: $self->{beat}\n";
131             $output .= "Key : $self->{key}\n";
132             my ( $cntr ) = 0;
133             foreach my $bar ( @{$self->{chord_progress}} ){
134             $output .= sprintf("| %-8s", $bar);
135             $cntr++;
136             if ( $cntr % 4 eq 0 ) {
137             $output .= "|\n";
138             }
139             }
140             print $output . "\n";
141             }
142             #- 拍とビートのチェック(制約)
143             sub check_time_and_beat {
144             my ( $self, $beat, $time ) = @_;
145             if ( ( $beat >= 8 ) && ( $time ne '8/8' ) ) {
146             print "Error: 8 or 16 beat must be used in 8/8 time music.\n";
147             exit;
148             }
149             }
150              
151             #- コード進行をパターンから生成
152             sub mk_chord_progress {
153             my $self = shift;
154             $self->check_time_and_beat($self->{beat}, $self->{time});;
155             my $cp; #- array ref
156             if ( $self->{pattern} eq 'pachelbel' ) {
157             $self->{chord_progress} = ['I V/VII', 'VIm IIIm/V', 'IV I/III', 'IV/II V7'];
158             } elsif ( $self->{pattern} eq 'blues' ) {
159             $self->{chord_progress} = ['I', 'I', 'I', 'I', 'IV', 'IV', 'I', 'I', 'V', 'IV', 'I', 'V7'];
160             } elsif ( $self->{pattern} eq 'vamp' ) {
161             $self->{chord_progress} = ['I', 'I', 'IV', 'IV', 'I', 'I', 'IV', 'IV'];
162             } elsif ( $self->{pattern} eq 'icecream' ) {
163             $self->{chord_progress} = ['I', 'VIm', 'IIm', 'V7', 'I', 'VIm', 'IIm', 'V7'];
164             } elsif ( $self->{pattern} eq 'major3' ) {
165             $self->{chord_progress} = ['bVI', 'bVII', 'I', 'I'];
166             } elsif ( $self->{pattern} eq 'iwantyouback' ) {
167             $self->{chord_progress} = ['I','IV','VIm I/III IVM7 I','IIm7 V7 I I'];
168             }
169             if ( $self->{tension} ) {
170             $self->add_tension;
171             }
172             $self->adjust_keys;
173             }
174              
175             #- キーに合わせる
176             sub adjust_keys {
177             my ( $self ) = shift;
178             my ( $wholetone ) = ['C','C#', 'D', 'Eb', 'E', 'F', 'F#','G', 'Ab', 'A', 'Bb', 'B'];
179             my ( $relative_tone ) = {
180             'I' => 0,
181             '#I' => 1,
182             'II' => 2,
183             'bIII' => 3,
184             'III' => 4,
185             'IV' => 5,
186             '#IV'=>6,
187             'V' => 7,
188             'bVI'=>8,
189             'VI'=>9,
190             'bVII' => 10,
191             'VII' => 11
192             };
193             $wholetone = $self->arrange_order( $wholetone );
194             my ( $many_chords ) = 0;
195             my ( $pedal_chords ) = 0;
196             foreach my $bar ( @{$self->{chord_progress}} ) {
197             my @chords;
198             if ( $bar =~ /\s+/ ) {
199             @chords = split (/\s+/, $bar);
200             $many_chords = 1;
201             } else {
202             push @chords, $bar;
203             }
204             foreach my $chord ( @chords ) {
205             my ( @notes );
206             if ( $chord =~ /\// ) {
207             @notes = split (/\//, $chord );
208             $pedal_chords = 1;
209             } else {
210             push @notes, $chord;
211             }
212             foreach my $note ( @notes ) { #- 1コードレベル
213             my ( $minor_Major );
214             if ( $note =~ /([mM\d]+)$/ ) {
215             $minor_Major = $1;
216             $note =~ s/$minor_Major//;
217             }
218             my ( $pntr ) = $relative_tone->{$note};
219             if ( $minor_Major ) {
220             $note = $wholetone->[$pntr] . $minor_Major;
221             } else {
222             $note = $wholetone->[$pntr];
223             }
224             }
225             if ( $pedal_chords ) {
226             $chord = join ('/', @notes);
227             } else {
228             $chord = $notes[0];
229             }
230             }
231             if ( $many_chords ) {
232             $bar = join (' ', @chords);
233             } else {
234             $bar = $chords[0];
235             }
236             }
237              
238             }
239              
240             #- ホールトーンスケールの順序を変える
241             sub arrange_order {
242             my ( $self, $wholetone ) = @_;
243             my ( $neworder ) = [];
244             my ( @tmparray_before, @tmparray );
245             my ( $done ) = 0;
246             for ( my $i = 0; $i <= $#$wholetone; $i++ ) {
247             if ( $self->{key} eq $wholetone->[$i] ) {
248             $done = 1;
249             push @tmparray, $wholetone->[$i];
250             } elsif ( $done < 1 ) {
251             push @tmparray_before, $wholetone->[$i];
252             } else {
253             push @tmparray, $wholetone->[$i];
254             }
255             }
256             push @tmparray, @tmparray_before;
257             $neworder = \@tmparray;
258             return $neworder;
259             }
260              
261             #- テンションをつける
262             sub add_tension {
263             my ( $self ) = shift;
264             my ( $tension_notes ) = {
265             #- 適当
266             'I' => ['6', '69', 'M7', 'M79'],
267             '#I' => [],
268             'II' => ['7'],
269             'bIII' => ['7'],
270             'III' => ['7'],
271             'IV' => ['M7', 'M79', 'M713'],
272             '#IV'=> [],
273             'V' => ['7', '79', '713'],
274             'bVI'=>['7'],
275             'VI'=>['7'],
276             'bVII' => ['7'],
277             'VII' => [],
278             };
279             my ( $many_chords ) = 0;
280             my ( $pedal_chords ) = 0;
281             foreach my $bar ( @{$self->{chord_progress}} ) {
282             my @chords;
283             if ( $bar =~ /\s+/ ) {
284             @chords = split (/\s+/, $bar);
285             $many_chords = 1;
286             } else {
287             push @chords, $bar;
288             }
289             foreach my $chord ( @chords ) {
290             my $pedal_chord;
291             if ( $chord =~ '/' ) {
292             ( $chord, $pedal_chord) = split ('/', $chord);
293             }
294             $chord =~ s/\d+$//g;
295             my ( $minor_Major );
296             if ( $chord =~ /([mM])$/ ) {
297             $minor_Major = $1;
298             $chord =~ s/$minor_Major//;
299             }
300             #- def tension
301             my $tension = '';
302             for ( my $i = ($self->{tension} - 1); $i >= 0; $i-- ) {
303             if ( $tension_notes->{$chord}->[$i] ) {
304             $tension = $tension_notes->{$chord}->[$i];
305             last;
306             }
307             }
308             if ( $minor_Major ) {
309             $chord .= $minor_Major . $tension;
310             } else {
311             $chord .= $tension;
312             }
313             # bug patch :-)
314             $chord =~ s/MM/M/;
315             $chord =~ s/mm/m/;
316              
317             if ( $pedal_chord ) {
318             $chord .= '/' . $pedal_chord;
319             }
320             }
321             if ( $many_chords ) {
322             $bar = join (' ', @chords);
323             } else {
324             $bar = $chords[0];
325             }
326             }
327             }
328              
329             #- ギタータブ譜を書く
330             sub guitar_tab {
331             my $self = shift;
332             my $one_bar_str = 1;
333             my $guitar_str = [qw(e B G D A E)];
334             my $tab = +{};
335             my $print_out_block = +{}; #-書き出し用単位
336             my $beat_tick = +{};
337             my $tab_blocks = 0;
338             #- 拍子で長さを決める。フォーマトbuild_tab_format;
339             my ( $child, $mother, $one_bar_length, $one_beat_length, $one_row, $one_bar_tick ) = $self->build_tab_format;
340             my $bar_cnt = 0;
341             my $bars_by_one_row = $self->{bars_by_one_row};
342             #- コード進行に応じた一小節ごとのループ
343             for my $bar ( @{$self->{chord_progress}} ) {
344             #- 一行目のコード進行表示部分
345             if ( $bar_cnt % $bars_by_one_row == 0 ) {
346             $print_out_block->{$tab_blocks} .= ' ';
347             } else {
348             $print_out_block->{$tab_blocks} .= ' ';
349             }
350             my ( @chords );
351             if ( $bar =~ / / ) {
352             @chords = split (/ /, $bar);
353             } else {
354             push @chords, $bar;
355             }
356             my ( $chords_in_one_bar ) = $#chords + 1; #-一小節内のコード数
357             my ( $bytes_for_one_chord ) = int( $one_bar_length / $chords_in_one_bar ); #- 3つあるときは?? #-ひとつのコードごとに持つ拍数
358             my ( $chord_num ) = 0;
359             for my $chord ( @chords ) {
360             my $format = '%-' . $bytes_for_one_chord . 's';
361             $print_out_block->{$tab_blocks} .= sprintf($format, $chord);
362             }
363             if ( $bar_cnt % $bars_by_one_row == ($bars_by_one_row -1) ) {
364             $print_out_block->{$tab_blocks} .= "\n";
365             }
366             #- 以上ヘッダづくり
367             my $string_num = 0;
368             #- ギターの弦ごとのループ
369             for my $string ( @{$guitar_str} ) {
370             my $one_tab_row = $one_row;
371             #- コードの内容に応じて、指をおく。
372             my ( $chord_num ) = 0;
373             for my $chord ( @chords ) {
374             my ( $chords_in_one_bar ) = $#chords + 1; #-一小節内のコード数
375             my ( $bytes_for_one_chord ) = int( $one_bar_length / $chords_in_one_bar ); #-ひとつのコードごとに持つ拍数
376             if ( $chord =~ /(\/[A-Z#b]+)/ ) {
377             $chord =~ s/$1//g;
378             }
379             if ( ( defined $guitar_chords->{standard}->{$chord}->[$string_num] ) && ( $guitar_chords->{standard}->{$chord}->[$string_num] ne '' )) {
380             #- コードが明示されていない場合、相対的に決めるルーチンも欲しい。
381             my $string_len = length ( $guitar_chords->{standard}->{$chord}->[$string_num] );
382             #- 置き換え位置をここで決めている。
383             #- 強拍は一応押さえる。
384             for ( my $j = 0; $j < $bytes_for_one_chord; $j++ ) {
385             if ( ( $self->{beat} == 2 ) or ( $self->{beat} == 4) ) {
386             #- 拍の頭なら
387             if ( $j % $one_beat_length == 0 ) {
388             my $offset = $self->return_offset($self->{beat}, $bytes_for_one_chord, $chord_num, $j);
389             #- 弦を押さえる。
390             substr($one_tab_row, $offset, $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
391             }
392             } elsif ( $self->{beat} == 8 ) {
393             #- 強拍
394             if ( ( $bytes_for_one_chord >= ( $one_beat_length * 4 ) ) && ( $j % ( $one_beat_length * 4 ) == 0 )) {
395             #- 1コードが2分音符以上続く場合
396             my $offset = $self->return_offset($self->{beat}, $bytes_for_one_chord, $chord_num, $j);
397             #- 強拍
398             substr($one_tab_row, $offset, $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
399             substr($one_tab_row, ($offset + 16) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
400             #- 弱拍の考慮 mute beat
401             substr($one_tab_row, ($offset + 12), $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
402             substr($one_tab_row, ($offset + 28), $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
403             } elsif ( ( $bytes_for_one_chord = $one_beat_length ) && ( $j % $one_beat_length == 0 )) {
404             #- 1コード一つの四分音符の場合
405             my $offset = $self->return_offset($self->{beat}, $bytes_for_one_chord, $chord_num, $j);
406             substr($one_tab_row, $offset, $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
407             #- 弱拍
408             substr($one_tab_row, ($offset + 4), $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
409              
410             }
411             } elsif ( $self->{beat} == 16) {
412             if ( $string_num >= 3 ) {
413             #- 16ビートの場合、第四弦以下は弾かない。
414             next;
415             }
416             #- 強拍
417             if ( $j % ( $one_beat_length * 4 ) == 0 ) {
418             #- あくまでもサンプルカッティング(センスよくしたいw
419             my $offset = $self->return_offset($self->{beat}, $bytes_for_one_chord, $chord_num, $j);
420             substr($one_tab_row, $offset, $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
421             substr($one_tab_row, ( $offset + 2) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
422             substr($one_tab_row, ( $offset + 4) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
423             if ( $bytes_for_one_chord >= ( $one_beat_length * 4 ) ) {
424             substr($one_tab_row, ( $offset + 8 ) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
425             substr($one_tab_row, ( $offset + 10 ) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
426             substr($one_tab_row, ( $offset + 14 ) , $string_len, $guitar_chords->{standard}->{$chord}->[$string_num]);
427              
428             }
429             }
430             }
431             }
432             }
433             $chord_num++;
434             }
435              
436             if ( $bar_cnt % $bars_by_one_row == 0 ) {
437             $tab->{$bar_cnt}->{$string} = "$string:$one_tab_row|"; #- 譜面を書く
438             } else {
439             $tab->{$bar_cnt}->{$string} = "$one_tab_row|"; #- 譜面を書く
440             }
441             #- 最後に来て、かつ2ブロック目ならまとめて書きだしハッシュを作る
442             if (( $bar_cnt % $bars_by_one_row == ( $bars_by_one_row - 1)) && ( $#$guitar_str == $string_num )) {
443             #- 一拍ごとの区切りをつける
444             $print_out_block->{$tab_blocks} .= ' ';
445             for ( my $i = 0; $i < $bars_by_one_row; $i++ ) {
446             $print_out_block->{$tab_blocks} .= ' '. $one_bar_tick;
447             }
448             $print_out_block->{$tab_blocks} .= "\n";
449             #- 各弦ごとのタブを連結
450             for my $Str ( @{$guitar_str} ) {
451             for my $i ( sort {$a<=>$b} keys %$tab ) {
452             $print_out_block->{$tab_blocks} .= $tab->{$i}->{$Str};
453             }
454             $print_out_block->{$tab_blocks} .= "\n";
455             }
456             $tab_blocks++;
457             $tab = undef;
458             }
459             $string_num++;
460             }
461             $bar_cnt++;
462             }
463             #- 出力する
464             for my $cnt ( sort {$a<=>$b} keys %$print_out_block ) {
465             print $print_out_block->{$cnt};
466             }
467             }
468              
469             #-弦上のオフセット戻し
470             sub return_offset {
471             my ( $self, $beat, $bytes_for_one_chord, $chord_num, $j) = @_;
472             if ( $beat !~ /^\d+$/ ) {
473             die "beat must be number: $beat";
474             }
475             if ( ( $beat == 2 ) || ( $beat == 4) || ( $beat == 16 )) {
476             return ( 1 + ($bytes_for_one_chord * $chord_num ) + $j );
477             } elsif ( $beat == 8) {
478             return ( 1 + ($bytes_for_one_chord * $chord_num * 2 ) + $j );
479             }
480              
481              
482             }
483              
484             #- 一小節のフォーマットづくり
485             sub build_tab_format {
486             my $self = shift;
487             my ( $one_bar_length, $one_beat_length, $one_row, $one_bar_tick);
488             my ( $child, $mother ) = split ('/', $self->{time} );
489             if ( ( $mother == 4 ) || ( $mother == 2) ) {
490             $one_bar_length = $mother * $child ;
491             } elsif ( ( $mother == 8 ) || ( $mother == 16 ) ) {
492             $one_bar_length = ( $mother * $child ) / 2 ;
493             }
494             $one_beat_length = $one_bar_length / $child;
495             for ( my $i = 0; $i < $one_bar_length; $i++ ) {
496             $one_row .= '-';
497             if ( ( $self->{beat} =~ /^(2|4)$/ ) && ( $i % $one_beat_length == 0 ) ) {
498             $one_bar_tick .= '+';
499             } elsif ( ( $self->{beat} =~ /^(8|16)$/) && ( $i % 8 == 0 ) ) {
500             $one_bar_tick .= '+';
501             } elsif ( ( $self->{beat} =~ /^(8|16)$/ ) && ( $i % 4 == 0 ) ) {
502             $one_bar_tick .= '-';
503             } else {
504             $one_bar_tick .= ' ';
505             }
506             }
507             $one_row .= '-'; #-見やすくするため一つ足す
508             $one_bar_tick = ' ' . $one_bar_tick;
509             return ( $child, $mother, $one_bar_length, $one_beat_length, $one_row, $one_bar_tick);
510             }
511              
512             1;
513             __END__