File Coverage

blib/lib/Data/BiaB/MIDI.pm
Criterion Covered Total %
statement 23 105 21.9
branch 0 28 0.0
condition 0 14 0.0
subroutine 9 10 90.0
pod 0 1 0.0
total 32 158 20.2


line stmt bran cond sub pod time code
1             #! perl
2              
3             package Data::BiaB::MIDI;
4              
5             BEGIN {
6 1     1   997 our $VERSION = 0.11;
7             }
8              
9 1     1   7 use Data::BiaB;
  1         2  
  1         42  
10 1     1   44 BEGIN { Data::BiaB->VERSION($VERSION) }
11              
12             =head1 NAME
13              
14             Data::BiaB::MIDI - MIDI generator for Data::BiaB
15              
16             =head1 SYNOPSIS
17              
18             This module provides MIDI generation for Data::BiaB.
19              
20             Example:
21              
22             use Data::BiaB;
23             use Data::BiaB::MIDI;
24              
25             # Load an existing song.
26             my $biab = Data::BiaB->new();
27             $biab->load("Vaya_Con_Dios.mgu");
28              
29             # Create MIDI.
30             $biab->makemidi("Vaya_Con_Dios.midi");
31              
32             For convenience, you can run the module from the command line:
33              
34             perl lib/Data/BiaB/MIDI.pm Vaya_Con_Dios.mgu
35              
36             This will produce a MIDI file named C<__new__.midi>.
37              
38             =cut
39              
40             package Data::BiaB;
41              
42 1     1   6 use warnings;
  1         2  
  1         25  
43 1     1   5 use strict;
  1         2  
  1         21  
44 1     1   4 use Carp qw( carp croak );
  1         2  
  1         618  
45              
46             my @keys =
47             ( '/','C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B',
48             'C#','D#','F#','G#','A#',
49             'Cm','Dbm','Dm','Ebm','Em','Fm','Gbm','Gm','Abm','Am','Bbm','Bm',
50             'C#m','D#m','F#m','G#m','A#m',
51             );
52              
53             my %chords =
54             (
55             "2" => [ qw( 0 2 7 ) ],
56             "sus2" => [ qw( 0 2 7 ) ],
57             "dim" => [ qw( 0 3 6 ) ],
58             "0" => [ qw( 0 3 6 ) ],
59             "dim7" => [ qw( 0 3 6 9 ) ],
60             "m7b5" => [ qw( 0 3 6 10 ) ],
61             "ΓΈ" => [ qw( 0 3 6 10 ) ],
62             "m7b5" => [ qw( 0 3 6 10 ) ],
63             "m9b5" => [ qw( 0 3 6 11 14 ) ],
64             "m" => [ qw( 0 3 7 ) ],
65             "m#5" => [ qw( 0 3 7 ) ],
66             "m6" => [ qw( 0 3 7 9 ) ],
67             "m69" => [ qw( 0 3 7 9 14 ) ],
68             "m7" => [ qw( 0 3 7 10 ) ],
69             "m9" => [ qw( 0 3 7 10 14 ) ],
70             "m11" => [ qw( 0 3 7 10 14 17 ) ],
71             "m13" => [ qw( 0 3 7 10 14 17 21 ) ],
72             "mmaj7" => [ qw( 0 3 7 11 ) ],
73             "mM7" => [ qw( 0 3 7 11 ) ],
74             "maug" => [ qw( 0 3 8 ) ],
75             "m7#5" => [ qw( 0 3 8 10 ) ],
76             "7b5" => [ qw( 0 4 6 10 ) ],
77             "7b5b9" => [ qw( 0 4 6 10 13 ) ],
78             "9b5" => [ qw( 0 4 6 10 14 ) ],
79             "13b5" => [ qw( 0 4 6 10 14 17 21 ) ],
80             "7b5#9" => [ qw( 0 4 6 10 15 ) ],
81             "maj7#5" => [ qw( 0 4 6 11 ) ],
82             "" => [ qw( 0 4 7 ) ],
83             "maj" => [ qw( 0 4 7 ) ],
84             "6" => [ qw( 0 4 7 9 ) ],
85             "maj6" => [ qw( 0 4 7 9 ) ],
86             "maj69" => [ qw( 0 4 7 9 14 ) ],
87             "69" => [ qw( 0 4 7 9 14 ) ],
88             "7" => [ qw( 0 4 7 10 ) ],
89             "7b9" => [ qw( 0 4 7 10 13 ) ],
90             "13b9" => [ qw( 0 4 7 10 13 17 21 ) ],
91             "7b9#11" => [ qw( 0 4 7 10 13 18 ) ],
92             "9" => [ qw( 0 4 7 10 14 ) ],
93             "7#9" => [ qw( 0 4 7 10 14 ) ],
94             "11" => [ qw( 0 4 7 10 14 17 ) ],
95             "7b13" => [ qw( 0 4 7 10 14 17 20 ) ],
96             "9b13" => [ qw( 0 4 7 10 14 17 20 ) ],
97             "13" => [ qw( 0 4 7 10 14 17 21 ) ],
98             "13sus" => [ qw( 0 4 7 10 14 17 21 ) ],
99             "13+" => [ qw( 0 4 7 10 14 17 22 ) ],
100             "7#11" => [ qw( 0 4 7 10 14 18 ) ],
101             "9#11" => [ qw( 0 4 7 10 14 18 ) ],
102             "13#11" => [ qw( 0 4 7 10 14 18 21 ) ],
103             "7#9b13" => [ qw( 0 4 7 10 15 17 20 ) ],
104             "13#9" => [ qw( 0 4 7 10 15 17 21 ) ],
105             "maj7" => [ qw( 0 4 7 11 ) ],
106             "M7" => [ qw( 0 4 7 11 ) ],
107             "maj7" => [ qw( 0 4 7 11 ) ],
108             "maj9" => [ qw( 0 4 7 11 14 ) ],
109             "maj9" => [ qw( 0 4 7 11 14 ) ],
110             "maj11" => [ qw( 0 4 7 11 14 17 ) ],
111             "maj13" => [ qw( 0 4 7 11 14 17 21 ) ],
112             "maj13" => [ qw( 0 4 7 11 14 17 21 ) ],
113             "maj9#11" => [ qw( 0 4 7 11 14 18 ) ],
114             "maj13#11" => [ qw( 0 4 7 11 14 18 21 ) ],
115             "aug" => [ qw( 0 4 8 ) ],
116             "+" => [ qw( 0 4 8 ) ],
117             "aug7" => [ qw( 0 4 8 10 ) ],
118             "7#5" => [ qw( 0 4 8 10 ) ],
119             "7#5" => [ qw( 0 4 8 10 ) ],
120             "7+" => [ qw( 0 4 8 10 ) ],
121             "7#5b9" => [ qw( 0 4 8 10 13 ) ],
122             "9#5" => [ qw( 0 4 8 10 14 ) ],
123             "7#5#9" => [ qw( 0 4 8 10 15 ) ],
124             "sus" => [ qw( 0 5 7 ) ],
125             "sus4" => [ qw( 0 5 7 ) ],
126             "4" => [ qw( 0 5 7 ) ],
127             "sus7" => [ qw( 0 5 7 10 ) ],
128             "7sus" => [ qw( 0 5 7 10 ) ],
129             "7susb9" => [ qw( 0 5 7 10 13 ) ],
130             "7sus#9" => [ qw( 0 5 7 10 15 ) ],
131             "7sus#5" => [ qw( 0 5 8 10 ) ],
132             "5b" => [ qw( 0 6 ) ],
133             "5" => [ qw( 0 7 ) ],
134             );
135              
136             my @midikeys = (split(/ /, "C G D A E B F# C# Cb Gb Db Ab Eb Bb F"));
137             my @midinotess = (split(/ /, "C C# D D# E F G G# A A# B"));
138             my @midinotesf = (split(/ /, "C Db D Eb E F G Ab A Bb B"));
139             my %midinotes;
140              
141             sub makemidi {
142 0     0 0   my ( $self, $file ) = @_;
143              
144 0           require MIDI;
145 1     1   7 use constant EV_TIME => 1;
  1         3  
  1         75  
146 1     1   7 use constant TICKS => 120;
  1         2  
  1         1012  
147              
148 0 0         unless ( %midinotes ) {
149 0           for ( my $i = 0; $i < @midinotess; $i++ ) {
150 0           $midinotes{$midinotess[$i]} = $i;
151 0           $midinotes{$midinotesf[$i]} = $i;
152             }
153             }
154              
155 0           my $bpm = 4; # beats per measure
156              
157 0           my $key = chordroot($self->{key_nr});
158 0           warn("key=$key");
159 0           my $minor = 0;
160              
161 0 0         if ( $key =~ /^(.+)m$/ ) {
162 0           $minor++;
163 0           $key = $1;
164             }
165 0           $key = $midinotes{$key};
166 0           warn("key=$key");
167 0 0         $key = 14 - $key if $key > 7;
168 0           warn("key=$key $minor");
169             my @pre = (
170 0           [ 'set_tempo', 0, 60000000 / $self->{bpm} ],
171             [ 'time_signature', 0, $bpm, 2, 24, 8 ],
172             [ 'key_signature', 0, $key, $minor ],
173             );
174              
175 0           my @ev;
176 0           my $time = 0;
177 0           my ( $onset, $chan, $pitch, $velo, $flags, $dur );
178 0           $onset = $bpm*TICKS;
179 0           $chan = 2;
180 0           $velo = 40;
181 0           $dur = TICKS;
182 0           my $chord;
183 0           my $beats = 0;
184              
185 0           my @chords;
186             # The chords consist of three parts:
187             # - the intro
188             # - the repeatable part (chorus)
189             # - the coda
190             # The starting and ending bar numbers for the chorus are known.
191              
192 0           my @c = @{$self->{chords}};
  0            
193 0           my $start = $self->{start_chorus_bar};
194 0           my $end = $self->{end_chorus_bar};
195              
196             # Start with the intro, if any.
197 0 0         push( @chords, @c[ 0 .. $bpm*$start-1] ) if $start > 1;
198              
199             # Append the chorus repetitions.
200 0           for ( my $r = $self->{number_of_repeats}; $r > 0; $r-- ) {
201 0           push( @chords, @c[ $bpm*($start-1) .. $bpm*$end-1 ] );
202             }
203              
204             # Append coda part, if any.
205 0 0         push( @chords, @c[ $bpm*$end .. $#c ] ) if $end < $#c;
206              
207             # Now turn the chords into a MIDI track.
208 0           @ev = ();
209 0           foreach ( @chords ) {
210 0 0         $chord = $_ if defined; # undefined -> use previous
211              
212 0 0         if ( ++$beats > $bpm ) {
213             # There are (always?) 4 chord slots per measure.
214 0 0         next if $beats < 4;
215 0           $beats = 0;
216             }
217              
218 0           $time += TICKS;
219              
220 0           my ( $root, $name, $type ) =
221             $chord =~ /^\s*(\d+)\s+\d+\s+(\S+)\s+(.*)/;
222 0           my @notes = @{$chords{$type}};
  0            
223 0 0         unless ( @notes ) {
224 0           warn("Unknown chord[$chord @ $time]: $name$type\n");
225 0           $onset += TICKS;
226 0           next;
227             }
228              
229             warn("CHORD[$time $chord] $root $name$type (@notes)\n")
230 0 0 0       if defined && $self->{debug};
231              
232             # All chord notes on ...
233 0           foreach ( @notes ) {
234             # 60 = central C.
235 0           push( @ev, [ 'note_on', $onset, $chan, 60+$_+$root-1, $velo ] );
236 0           $onset = 0;
237             }
238             # ... and off a beat later.
239 0           $onset = TICKS;
240 0           foreach ( @notes ) {
241 0           push( @ev, [ 'note_off', $onset, $chan, 60+$_+$root-1, $velo ] );
242 0           $onset = 0;
243             }
244             }
245              
246             # Add preamble and make a track.
247 0           my $chords = MIDI::Track->new( { events => [ @pre, @ev ] } );
248 0           my @tracks = ( $chords );
249              
250             # Now for the melody.
251 0           @ev = ();
252 0           foreach ( @{ $self->{melody} } ) {
  0            
253 0           ( $onset, $chan, $pitch, $velo, $flags, $dur ) = @$_;
254              
255             # Skip notes we won't (cannot) handle.
256 0 0 0       next unless $flags == 144 || $flags == 148 || $flags == 147;
      0        
257 0 0 0       next unless $chan && $chan < 16;
258              
259             # Subtrackt lead in.
260 0           $onset -= $bpm*TICKS;
261              
262             # Make MIDI.
263 0           push( @ev, [ 'note_on', $onset, $chan, $pitch, $velo ] );
264 0           push( @ev, [ 'note_off', $onset+$dur, $chan, $pitch, $velo ] );
265             }
266              
267 0 0         unless ( @ev ) {
268 0           carp("No melody?");
269             }
270             else {
271             # Sort on timestamp.
272 0           @ev = sort { $a->[EV_TIME] <=> $b->[EV_TIME] } @ev;
  0            
273              
274             # Convert to delta times.
275 0           $time = 0;
276 0           foreach my $e ( @ev ) {
277 0 0         carp("NEGATIVE DELTA \@ $time: @{[$e->[EV_TIME]-$time]}\n")
  0            
278             if $e->[EV_TIME] < $time;
279             # Make time relative.
280 0           ($time, $e->[EV_TIME]) = ($e->[EV_TIME], $e->[EV_TIME]-$time);
281             }
282              
283             # Create a MIDI track and add it to the tracks.
284 0           my $melody = MIDI::Track->new( { events => [ @pre, @ev ] } );
285 0           push( @tracks, $melody );
286             }
287              
288             # Create the MIDI Opus.
289 0           my $op = MIDI::Opus->new
290             ( { format => 1,
291             ticks => TICKS,
292             tracks => \@tracks,
293             } );
294              
295             # And save.
296 0   0       $op->write_to_file( $file || '__new__.midi' );
297             }
298              
299              
300             1; # End of Data::BiaB
301              
302             package main;
303              
304             unless ( caller ) {
305 1     1   9 use Data::BiaB;
  1         2  
  1         115  
306             my $b = Data::BiaB->new( debug => 1 )->load (shift )->parse;
307             $b->makechords;
308             $b->makemidi;
309             }
310              
311             =head1 AUTHOR
312              
313             Johan Vromans, C<< >>
314              
315             =head1 BUGS AND SUPPORT
316              
317             See L.
318              
319             =head1 COPYRIGHT & LICENSE
320              
321             Copyright 2016 Johan Vromans, all rights reserved.
322              
323             This program is free software; you can redistribute it and/or modify
324             it under the same terms as Perl itself.
325              
326             =cut
327              
328             1;