File Coverage

blib/lib/Music/Note.pm
Criterion Covered Total %
statement 91 98 92.8
branch 51 72 70.8
condition 23 36 63.8
subroutine 12 12 100.0
pod 4 9 44.4
total 181 227 79.7


line stmt bran cond sub pod time code
1             package Music::Note;
2 4     4   57435 use strict;
  4         9  
  4         179  
3            
4             BEGIN {
5 4     4   20 use vars qw ($VERSION);
  4         5  
  4         217  
6 4     4   6529 $VERSION = 0.01;
7             }
8            
9             =head1 NAME
10            
11             Note - representation of a single musical note, and various manipulation routines
12            
13             =head1 SYNOPSIS
14            
15             use Music::Note;
16            
17             my $note = Music::Note->new("C#4","ISO"); # 'simple' style - notename/type
18             $note->transpose->(-16); # to A2
19             $note->alter(1); # to A#2
20             $note->en_eq('flat'); # to Bb2
21             print $note->format("kern"); # BB-
22             $note = Music::Note->new({step=>'C',octave=>'4',alter=>1});
23             print $note->octave(); # 4
24             print $note->step(); # C
25            
26            
27             =head1 DESCRIPTION
28            
29             An OO encapsulation of a single musical note. Includes methods for transposition, enharmonic equivalence, and creation/output in a range of formats.
30            
31             =head1 METHODS
32            
33             =head2 new($notename,$type)
34            
35             Creates a new Note object. See below for a list of types. $type defaults to "ISO" if omitted. $notename defaults to "C4".
36            
37             =head2 new({%params})
38            
39             Creates a new Note object. Parameters are MusicXML compliant - viz. "step" (A-G), 'octave' (+/- integer) and 'alter' (+/- integer).
40             The 'octave' parameter is based upon standard usage, where C4=Middle C and the number changes between B and C. The 'alter' parameter indicates
41             accidentals, where '1' is sharp, '-1' is flat, '2' is double-sharp and so on.
42            
43             =head2 format($type)
44            
45             Returns the note object formatted in one of the styles named below.
46            
47             =head2 transpose($amount)
48            
49             Transposes the note by $amount semitones (+/-), keeping accidental types where possible (eg. - Ab + 2 = Bb, not A#).
50             Returns the note object.
51            
52             =head2 en_eq($type)
53            
54             Changes the Note into an enharmonic equivalent (C#->Db, for instance).
55             $type can be either 'sharp' (or 's'/'#') or 'flat'('f'/'b').
56             Double accidentals are naturalised - so 'Bbb' will become 'A' if sharpened, and 'Fx' becomes 'G' if flattened.
57             Returns the note object.
58            
59             =head1 STYLES
60            
61             ISO (C3, D#4, Ab5)
62             isobase (C, Ds, Af (as ISO, but no octave number) )
63             midi (C3, Ds4, Af5)
64             midinum (0-127)
65             kern (CC, d+, aa-)
66             MusicXML (D41)
67             pdl (c3, ds4, af5)
68            
69             'xml' can be used as a synonym for 'MusicXML'.
70            
71            
72             =head1 TODO
73             more types - abc, solfa (do,re,mi,fa,so,la,ti), Indian (sa,re,ga,ma,pa,da,ni)
74             length manipulation
75            
76             =head1 AUTHOR
77            
78             Ben Daglish (bdaglish@cpan.org)
79            
80             =head1 BUGS
81            
82             None known
83             All feedback most welcome.
84            
85             =head1 COPYRIGHT
86            
87             Copyright (c) 2003, Ben Daglish. All Rights Reserved.
88             This program is free software; you can redistribute
89             it and/or modify it under the same terms as Perl itself.
90            
91             The full text of the license can be found in the
92             LICENSE file included with this module.
93            
94             =head1 SEE ALSO
95            
96             perl(1).
97            
98             =cut
99            
100             my %midinotes = qw(0 C 1 CS 2 D 3 DS 4 E 5 F 6 FS 7 G 8 GS 9 A 10 AS 11 B);
101             my %stepnums = qw(C 0 D 2 E 4 F 5 G 7 A 9 B 11);
102             my @numsteps = qw(C C D D E F F G G A A B C C D D E F F G G A A B C C D D E F F G G A A B);
103            
104             sub new {
105 11     11 1 58 my ($class) = shift();
106 11         44 my %self = ('step'=>'C','alter'=>0,'octave'=>4);
107 11         20 my ($note) = shift();
108 11 100       34 if (ref($note) =~ /HASH/) {
109 2         17 %self = (%self,%$note);
110             }
111             else {
112 9   100     131 my $type = lc(shift() || "iso");
113 9 100 100     95 if ($type eq 'iso' || $type eq 'isobase' || $type eq 'midi' || $type eq 'pdl') {
    100 100        
    100 100        
    50 66        
114 6         31 $note =~ /([A-Ga-g])([bn\#xfs]\b?)?([+-]?\d+)?/;
115 6 50       31 $self{step} = uc($1) if $1;
116 6 100       24 $self{octave} = $3 if defined $3;
117 6   50     24 my $alt = $2 || '';
118 6         18 $alt =~ s/f(?:lat)?/b/g;
119 6         11 $alt =~ s/s(?:harp)?/\#/g;
120 6 50       18 $self{alter} = -2 if $alt eq 'bb';
121 6 100       16 $self{alter} = -1 if $alt eq 'b';
122 6 100       19 $self{alter} = 1 if $alt eq '#';
123 6 50 33     34 $self{alter} = 2 if ($alt eq 'x' || $alt eq '##');
124             }
125             elsif ($type eq 'musicxml' || $type eq 'xml') {
126 1 50       10 if ($note =~ /(.*?)<\/step>/){$self{step} = $1 if $1;}
  1 50       7  
127 1 50       18 if ($note =~ /(.*?)<\/octave>/){$self{octave} = $1 if $1;}
  1 50       7  
128 1 50       8 if ($note =~ /(.*?)<\/alter>/){$self{alter} = $1 if $1;}
  1 50       6  
129             }
130             elsif ($type eq 'midinum') {
131 1         5 ($self{step},$self{octave},$self{alter}) = from_midinum($note);
132             }
133             elsif ($type eq 'kern') {
134 1         5 $note =~ /([a-gA-G]+)([\#-]*)/;
135 1         5 my ($step,$alt) = ($1,$2);
136 1 50       8 $self{alter} = length($alt) * (($alt =~/-/) ? -1 : 1);
137 1         4 $self{step} = uc(substr($step,0,1));
138 1         10 my $l = length($step) - 1;
139 1 50       5 if ($step eq uc($step)) {$l = -(++$l)}
  1         3  
140 1         3 $self{octave} = 4 + $l;
141             }
142             }
143 11         131 bless \%self,$class;
144             }
145            
146             sub step {
147 12     12 0 59 my $self = shift();
148 12 50 33     51 $self->{step} = shift() if (@_ && $_[0] =~ /^[A-G]$/);
149 12         66 $self->{step};
150             }
151             sub octave {
152 12     12 0 20 my $self = shift();
153 12 50 33     41 $self->{octave} = shift() if (@_ && $_[0] =~ /[+-]?\d+/);
154 12         53 $self->{octave};
155             }
156             sub alter {
157 13     13 0 23 my $self = shift();
158 13 100 66     59 $self->{alter} = shift() if (@_ && $_[0] =~ /[+-]?\d+/);
159 13         67 $self->{alter};
160             }
161            
162             sub format {
163 16     16 1 84 my ($self,$format) = @_;
164 16   100     39 $format = lc($format) || "iso";
165 16         70 my %isofs = (-3,'bbb',-2,'bb',-1,'b',0,'',1,'#',2,'x',3,'x#');
166 16         59 my %midifs = (-3,'fff',-2,'ff',-1,'f',0,'',1,'s',2,'ss',3,'sss');
167 16         49 my %kernfs = (-3,'---',-2,'--',-1,'-',0,'',1,'#',2,'##',3,'###');
168 16 100       32 if ($format eq 'iso') {
169 4         35 return $self->{step}.$isofs{$self->{alter}}.$self->{octave};
170             }
171 12 100 33     48 if ($format eq 'isobase') {
    100          
    100          
    100          
    100          
    50          
172 2         16 return $self->{step}.$isofs{$self->{alter}};
173             }
174             elsif ($format eq 'midi') {
175 2         15 return $self->{step}.$midifs{$self->{alter}}.$self->{octave};
176             }
177             elsif ($format eq 'pdl') {
178 2         23 return lc($self->{step}).$midifs{$self->{alter}}.$self->{octave};
179             }
180             elsif ($format eq 'midinum') {
181 2         15 return $self->to_midinum;
182             }
183             elsif ($format eq 'kern') {
184 2         5 my $s = $self->{step};
185 2         5 my $o = $self->{octave} - 4;
186 2 100       5 if ($o >= 0) {$s = lc($s);}
  1         4  
  1         2  
187             else {$o = -(++$o);}
188 2         19 $s.($s x $o).$kernfs{$self->{alter}};
189             }
190 0         0 elsif ($format eq 'xml' || $format eq 'musicxml') {
191 2         26 return "$self->{step}$self->{octave}$self->{alter}";
192             }
193             else {warn ("Incorrect format ($format) passed to Music::Note->format");}
194             }
195             sub transpose {
196 2     2 1 10 my ($self,$amount) = @_;
197 2         9 my $num = $self->to_midinum;
198 2         5 my $alt = $self->{alter};
199 2         3 $num += int($amount);
200 2         7 ($self->{step},$self->{octave},$self->{alter}) = from_midinum($num);
201 2 50 33     11 if ($alt < 0 && $self->{alter}) {
202 0         0 $self->en_eq('f');
203             }
204 2         6 $self;
205             }
206            
207             sub en_eq {
208 1     1 1 3 my ($self,$type) = @_;
209 1         4 my $stepnum = $stepnums{$self->{step}} + 12;
210 1 50       10 if ($type =~ /^[s\#]/) {
    50          
211 0         0 $self->{alter} += 2;
212 0         0 $stepnum -= 2;
213 0         0 $self->{step} = $numsteps[$stepnum];
214 0 0       0 $self->{octave}-- if ($stepnum < 12);
215             }
216 0         0 elsif ($type =~ /^[fb]/) {
217 1         3 $self->{alter} -= 2;
218 1         3 $stepnum += 2;
219 1         3 $self->{step} = $numsteps[$stepnum];
220 1 50       6 $self->{octave}++ if ($stepnum > 24);
221             }
222             else {warn ("Incorrect type ($type) passed to Music::Note->en_hq");}
223             }
224            
225             sub to_midinum {
226 4     4 0 6 my $self = shift();
227 4         34 return (12 * ($self->{octave}+1)) + $stepnums{$self->{step}} + $self->{alter};
228             }
229             sub from_midinum {
230 3     3 0 5 my $num = shift();
231 3         13 my $step = $midinotes{$num % 12};
232 3         11 my $octave = int($num / 12) - 1;
233 3         5 my $alter = 0;
234 3 100       20 if ($step =~ s/S$//) {$alter = 1;}
  2         4  
235 3         14 ($step,$octave,$alter);
236             }
237            
238            
239             1;
240