File Coverage

blib/lib/Audio/Beep.pm
Criterion Covered Total %
statement 29 99 29.2
branch 6 62 9.6
condition 1 10 10.0
subroutine 7 13 53.8
pod 5 5 100.0
total 48 189 25.4


line stmt bran cond sub pod time code
1             package Audio::Beep;
2              
3             $Audio::Beep::VERSION = 0.11;
4              
5 1     1   43346 use strict;
  1         2  
  1         46  
6 1     1   4 use Carp;
  1         1  
  1         161  
7 1     1   7 use Exporter;
  1         5  
  1         36  
8 1     1   4 use vars qw(%NOTES @PITCH @EXPORT @EXPORT_OK @ISA);
  1         1  
  1         1832  
9             @ISA = qw(Exporter);
10             @EXPORT = qw(beep);
11             @EXPORT_OK = qw(beep);
12              
13              
14             ### GLOBALS
15              
16             %NOTES = (
17             c => 0,
18             d => 2,
19             e => 4,
20             f => 5,
21             g => 7,
22             a => 9,
23             b => 11,
24             );
25              
26             @PITCH = (
27             261.6, 277.2,
28             293.6, 311.1,
29             329.6,
30             349.2, 370.0,
31             392.0, 415.3,
32             440.0, 466.1,
33             493.8,
34             );
35              
36              
37             ### OO METHODS
38              
39             sub new {
40 1     1 1 12 my $class = shift;
41 1 0 33     6 carp "Odd number of parameters where hash expected" if @_ % 2 and $^W;
42 1         4 my (%h) = @_;
43 1 50       5 if ( $h{player} ) {
44 0 0       0 $h{player} = _player_from_string( $h{player} ) unless ref $h{player};
45             } else {
46 1         7 $h{player} = _best_player();
47             }
48 1 50       16 carp "No player found. You should specify one before playing anything."
49             unless $h{player};
50 1         11 return bless \%h, $class;
51             }
52              
53             sub player {
54 1     1 1 3 my $self = shift;
55 1         21 my ($player) = @_;
56 1 0       4 $self->{player} = ref $player ? $player : _player_from_string($player)
    50          
57             if $player;
58 1         10 return $self->{player};
59             }
60              
61             sub rest {
62 0     0 1 0 my $self = shift;
63 0         0 my ($rest) = @_;
64 0 0       0 $self->{rest} = $rest if defined $rest;
65 0         0 return $self->{rest};
66             }
67              
68             sub play {
69 0     0 1 0 my $self = shift;
70 0         0 my ($music) = @_;
71            
72 0         0 my %p = (
73             note => 'c',
74             duration => 4,
75             octave => 0,
76             bpm => 120,
77             pitch_mod => 0,
78             dot => 0,
79             relative => 1,
80             transpose => 0,
81             );
82            
83 0         0 while ($music =~ /\G(?:([^\s#]+)\s*|#[^\n]*\n|\s*)/g) {
84 0 0       0 local $_ = $1 or next;
85            
86 0 0       0 if ( /^\\(.+)/ ) {
87 0         0 COMMAND: {
88 0         0 local $_ = $1;
89 0 0       0 /^(?:bpm|tempo)(\d+)/ and do {$p{bpm} = $1; last};
  0         0  
  0         0  
90 0 0       0 /^rel/ and do {$p{relative} = 1; last};
  0         0  
  0         0  
91 0 0       0 /^norel/ and do {$p{relative} = 0; last};
  0         0  
  0         0  
92 0 0       0 /^transpose([',]+)/ and do {
93 0         0 local $_ = $1;
94 0         0 $p{transpose} = tr/'/'/ - tr/,/,/;
95 0         0 last;
96             };
97 0 0       0 carp qq|Command "$_" is unparsable\n| if $^W;
98             }
99 0         0 next;
100             }
101            
102 0         0 my ($note, $mod, $octave, $dur, $dot) =
103             /^\W*([cdefgabr])(is|es|s)?([',]+)?(\d+)?(\.+)?\W*$/;
104            
105 0 0       0 unless ($note) {
106 0 0       0 carp qq|Note "$_" is unparsable\n| if $^W;
107 0         0 next;
108             }
109            
110 0 0       0 $p{duration} = $dur if $dur;
111              
112 0         0 $p{dot} = 0;
113 0 0       0 do{ $p{dot} += tr/././ for $dot } if $dot;
  0         0  
114            
115 0 0       0 if ( $note eq 'r' ) {
116 0         0 $self->player->rest( _duration(\%p) );
117             } else {
118 0 0       0 if ( $p{relative} ) {
119 0         0 my $diff = $NOTES{ $p{note} } - $NOTES{ $note };
120 0 0       0 $p{octave} += $diff < 0 ? -1 : 1 if abs $diff > 5;
    0          
121             } else {
122 0         0 $p{octave} = $p{transpose};
123             }
124            
125 0 0       0 do{ $p{octave} += tr/'/'/ - tr/,/,/ for $octave } if $octave;
  0         0  
126            
127 0         0 $p{pitch_mod} = 0;
128 0 0       0 $p{pitch_mod} = $mod eq 'is' ? 1 : -1 if $mod;
    0          
129            
130 0         0 $p{note} = $note;
131 0         0 $self->player->play( _pitch(\%p), _duration(\%p) );
132             }
133            
134 0 0       0 select undef, undef, undef, $self->{rest} / 1000 if $self->{rest};
135             }
136             }
137              
138              
139             ### UTILITIES
140              
141             sub _pitch {
142 0     0   0 my $p = shift;
143 0         0 return $PITCH[($NOTES{ $p->{note} } + $p->{pitch_mod}) % 12] *
144             (2 ** $p->{octave});
145             }
146              
147             sub _duration {
148 0     0   0 my $p = shift;
149 0         0 my $dur = 4 / $p->{duration};
150 0 0       0 if ( $p->{dot} ) {
151 0         0 my $half = $dur / 2;
152 0         0 for (my $i = $p->{dot}; $i--; ) {
153 0         0 $dur += $half;
154 0         0 $half /= 2;
155             }
156             }
157 0         0 return int( $dur * (60 / $p->{bpm}) * 1000 );
158             }
159              
160             sub _best_player {
161 1     1   9 my %os_modules = (
162             linux => [
163             'Audio::Beep::Linux::beep',
164             'Audio::Beep::Linux::PP',
165             ],
166             MSWin32 => [
167             'Audio::Beep::Win32::API',
168             ],
169             freebsd => [
170             'Audio::Beep::BSD::beep',
171             ],
172             );
173            
174 1         3 for my $mod ( @{ $os_modules{$^O} } ) {
  1         5  
175 2 50       146 if (eval "require $mod") {
176 2         13 my $player = $mod->new();
177 2 100       22 return $player if defined $player;
178             }
179             }
180              
181 0           return;
182             }
183              
184             sub _player_from_string {
185 0     0     my ($mod) = @_;
186 0           my $pack = __PACKAGE__;
187 0           $mod =~ s/^(${pack}::)?/${pack}::/;
188 0 0         eval "require $mod" or croak "Cannot load $mod : $@";
189 0           return $mod->new();
190             }
191              
192              
193             ### EXPORTED FUNCTIONS
194              
195             { #SCOPE FOR CACHED PLAYER
196              
197             my $player;
198              
199             sub beep {
200 0     0 1   my ($pitch, $duration) = @_;
201 0   0       $pitch ||= 440;
202 0   0       $duration ||= 100;
203 0 0 0       $player ||= _best_player() or croak "Couldn't find a working player";
204 0           $player->play($pitch, $duration);
205             }
206              
207             }
208              
209             1;