File Coverage

blib/lib/Term/Graille/Audio.pm
Criterion Covered Total %
statement 24 124 19.3
branch 0 40 0.0
condition 0 18 0.0
subroutine 8 22 36.3
pod 3 14 21.4
total 35 218 16.0


line stmt bran cond sub pod time code
1             =head1 NAME
2             Term::Graille::Audio
3              
4             Modal hierarchical Menu system
5              
6             =head1 SYNOPSIS
7            
8             use Term::Graille::Audio; # TERM::Graille's Audio module
9            
10             my $beep=Term::Graille::Audio->new();# create object;
11             $beep->playSound(undef, "A#1"); # use built-in samples to play a note
12              
13              
14              
15             =head1 DESCRIPTION
16              
17             Developed to use Audio in Braille Applications. Again the empahsis
18             to try and avoid external libraries. It does neeed some things to connect
19             with sound hardware though. Linux systems need pulseaudio utilities
20             Winodws is as yet untested. Windows systems need Win32::Sound
21              
22              
23             =begin html
24              
25            
26              
27             =end html
28              
29              
30             =head1 FUNCTIONS
31              
32             =cut
33              
34              
35             package Term::Graille::Audio;
36              
37 1     1   979 use strict; use warnings;
  1     1   2  
  1         27  
  1         4  
  1         2  
  1         27  
38 1     1   463 use IO::File;
  1         8421  
  1         110  
39 1     1   7 use Time::HiRes ("sleep"); # allow fractional sleeps
  1         2  
  1         6  
40 1     1   1121 use if $^O eq 'MSWin32', "Win32::Sound";
  1         14  
  1         7  
41 1     1   39 use Storable;
  1         2  
  1         45  
42 1     1   5 use utf8;
  1         2  
  1         5  
43              
44             our $VERSION= 0.01;
45              
46             our $dsp;
47              
48             =head3 Cnew(%params)>
49              
50             Creates a new audio interface object; params are
51             C samples stored in external files may be loaded allowing
52             different sounds to be played back. These are stored and retrieved
53             as Storable files. This is optional, an if not supplied,
54             Term::Graille::Audio gnerates its own sinwave sample.
55              
56             =cut
57              
58              
59             sub new{
60 0     0 1   my ($class,%params)=@_;
61 0           my $self={};
62 0           our $dsp;
63 0           bless $self, $class;
64 0           $self->{samples}={};
65 0 0         if ($params{samples}){
66 0 0         my @files=ref $params{samples}?@{$params{samples}}:($params{samples});
  0            
67 0           foreach my $file (@files){
68 0           my $name=$file; $name =~ s{^.*[/\\]|\.[^\.]+$}{}g;
  0            
69 0 0         $self->{samples}->{$name}=retrieve($file) if (-e $file)
70             }
71             }
72             else{
73 0           $self->makeSampleSet();
74             };
75 0           return $self;
76             }
77              
78             sub start{
79 0     0 0   my $self=shift;
80 0 0         if ($^O eq 'MSWin32'){
81 0           our $dsp = new Win32::Sound::WaveOut(8000, 8, 1);
82             }
83             else{
84 0 0         open(our $dsp,"|padsp tee /dev/dsp > /dev/null") or warn "DSP can not be intiated $!";
85             }
86             }
87              
88             sub makeSampleSet{ # generates full from C0 t0 B8 (96 notes)
89 0     0 0   my ($self,$name, $middleA, $sps,$type)=@_;
90 0   0       $name//="default";
91 0   0       $middleA//=419;
92 0   0       $sps//=1024;
93 0           my @octaves=("C","C#","D","D#","E","F","F#","G","G#","A","A#","B") x 8; # create 96 notes
94 0           my @keys= map{$octaves[$_].(int ($_/12)) }(0..$#octaves); # append the octave number
  0            
95 0           $self->{samples}->{$name}={keys=>\@keys,sps=>$sps,middleA=>$middleA};
96 0           for my $k(1..scalar @keys){
97 0           $self->{samples}->{$name}->{$keys[$k-1]}=makeNotes($self,$middleA,$sps,$k-58,$type);
98             }
99             }
100              
101              
102             sub record{
103 0     0 0   my ($self,$sps)=@_;
104 0           my ($buffer,$recording);
105 0 0         open (my $rec, "< :raw :bytes","|padsp /dev/dsp") or die "Could not open for recording $!";
106 0           binmode($rec);
107 0           $recording.=$buffer while (read($rec, $buffer, $sps));
108 0           close $rec;
109 0           return $recording;
110             }
111              
112              
113              
114             #A term::Graille Specific Piano Keyboard can be setup and drawn
115              
116             sub setupKeyboard{
117 0     0 0   my ($self,$canvas,$params)=@_;
118 0           $self->{canvas}=$canvas;
119 0           $self->{keyboard}={};
120 0           my %default;
121 0           @default{qw/top left vSep hSep/}=(55,20,4,8);
122 0           foreach (qw/top left vSep hSep/){
123 0   0       $self->{keyboard}->{$_}=$params->{$_}//$default{$_};
124             };
125 0           $self->{keys}={};
126 1     1   747 no warnings qw{qw};
  1         2  
  1         1338  
127             # my ($top,$left,$vSep,$hSep)=(55,20,4,8);
128 0           my @keyNotes=([[qw/1 2 3 4 5 6 7 8 9 0 - = /],
129             [qw/. C# D# . F# G# A# . C# D# . F#/]],
130             [[qw/q w e r t y u i o p [/],
131             [qw/C D E F G A B C D E F/]],
132             [[qw/a s d f g h j k l ; /],
133             [qw/G# A# . C# D# . F# G# A# . /]],
134             [[qw{\ z x c v b n m , . /}],
135             [qw/G A B C D E F G A B C/]],
136             );
137 0           my @rowShift=(0,4,8,4);
138 0           for my $keyRow (0..$#keyNotes){
139 0           my @keys=@{$keyNotes[$keyRow]->[0]};
  0            
140 0           my @notes=@{$keyNotes[$keyRow]->[1]};
  0            
141 0           foreach my $keyPos(0..$#keys){
142             $self->{keys}->{$keys[$keyPos]}={
143             x=>$self->{keyboard}->{hSep}*$keyPos+$self->{keyboard}->{left}+$rowShift[$keyRow],
144 0 0         y=>$self->{keyboard}->{top}-3*$self->{keyboard}->{vSep}*$keyRow,
    0          
145             n=>$notes[$keyPos],
146             c=>$keyRow%2?"black on_white":"white on_black",
147             } unless $notes[$keyPos] eq ".";
148             }
149             }
150             };
151              
152             sub drawKeyboard{
153 0     0 0   my ($self)=@_;
154 0           for my $key (keys %{$self->{keys}}){
  0            
155 0           $self->drawKey($key)
156             }
157             }
158              
159             sub drawKey{
160 0     0 0   my ($self,$key,$colour)=@_;
161 0 0 0       return unless $key && defined $self->{keys}->{$key};
162 0           my ($x,$y,$n,$c)=@{$self->{keys}->{$key}}{qw/x y n c/};
  0            
163 0 0         $c=$colour if $colour;
164 0           $self->{canvas}->textAt($x,$y,$key,$c);
165 0           $self->{canvas}->textAt($x+4,$y," ","reset");
166 0           $self->{canvas}->textAt($x,$y-4,$n,$c) ;
167 0           $self->{canvas}->textAt($x+4,$y-4," ","reset");
168             }
169              
170             sub makeKeyboard{
171 0     0 0   my ($self)=@_;
172            
173 0           my $keyboard=<
174              
175              
176             ┏━━━━━━ Perl Incredibly Annoyingly Noisy Organ ━━━━━━┓
177             ┃ ┃
178             ┃ 1 2 3 4 5 6 7 8 9 0 - = ┃
179             ┃ C# D# F# G# A# C# D# F# ┃
180             ┃ ┃
181             ┃ q w e r t y u i o p [ ┃
182             ┃ C D E F G A B C D E F ┃
183             ┃ ┃
184             ┃ a s d f g h j k l ; ' ┃
185             ┃ G# A# C# D# F# G# A# ┃
186             ┃ ┃
187             ┃ \\ z x c v b n m , . / ┃
188             ┃ G A B C D E F G A B C ┃
189             ┃ ┃
190             ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
191             EOK
192              
193 0           my $key2note={
194             2=>"C#3", 3=>"D#3", 5=>"F#3", 6=>"G#3", 7=>"A#3", 9=>"C#4", "0"=>"D#4", "="=>"F#4",
195             q=>"C3", w=>"D3", e=>"E3", r=>"F3", t=>"G3", y=>"A3", u=>"B3", i=>"C4", o=>"D4", p=>"E4", "["=>"F4",
196             s=>"G#4", d=>"A#4", f=>"C#5", g=>"D#5", j=>"F#5", k=>"G#5", l=>"A#5", "'"=>"C#6",
197             "\\"=>"G4", z=>"A4", x=>"B4", c=>"C5", v=> "D5", b=>"E5", n=>"F5", m=>"G5", ","=>"A5", "."=>"B5", "/"=>"C6",
198             };
199              
200 0           return ($keyboard,$key2note);
201             }
202              
203              
204              
205             =head3 CsaveSampleSet($name,$path)>
206              
207             Saves a sample set to file to directory (adds an extenison ".spl)
208              
209             =cut
210              
211             sub saveSampleSet{
212 0     0 1   my ($self,$name,$directory)=@_;
213 0           store($self->{samples}->{name}, $directory."/".$name.".spl");
214             }
215              
216             =head3 CsaveSampleSet($name,$path)>
217              
218             loads a sample set from full path, excludes the extension in the name;
219              
220             =cut
221              
222             sub loadSampleSet{
223 0     0 0   my ($self,$file)=@_;
224 0           my $name=$file; $name =~ s{^.*[/\\]|\.[^\.]+$}{}g;
  0            
225 0 0         $self->{samples}->{$name}=retrieve($file) if (-e $file)
226             }
227              
228             sub makeNotes{
229 0     0 0   my ($self, $middleA, $sps, $offset, $type)=@_;
230 0   0       $offset//=0;
231 0           my $f=$middleA*2**($offset/12);
232 0           my $s=pack'C*',map 127*(1+sin(($_*2*3.14159267*$f)/$sps)),0..$sps-1; # generate the sample
233 0           return {f=>sprintf("%.2f",$f),s=>$s};
234             }
235              
236              
237             =head3 CplaySound($name,$soundName)>
238              
239             Plays a note from the samplesset ($name). If sampleset is undefined then
240             the builtin-"default" sample set is used.
241              
242             =cut
243              
244             sub playSound{
245 0     0 1   my ($self, $seriesName, $soundName)=@_;
246 0   0       $seriesName//="default";
247 0           my $b;
248 0 0         if (exists $self->{samples}->{$seriesName}){
    0          
249             $b=$soundName=~/^\d/?
250             $self->{samples}->{$seriesName}->{$self->{samples}->{$seriesName}->{keys}->[$soundName]}->{s}:
251 0 0         $self->{samples}->{$seriesName}->{$soundName}->{s};
252             }
253             elsif (exists $self->{sounds}->{$seriesName}){
254             $b=$soundName=~/^\d/?
255             $self->{samples}->{$seriesName}->{$self->{samples}->{$seriesName}->{items}->[$soundName]}->{s}:
256 0 0         $b= $self->{sounds}->{$seriesName}->{$soundName}->{s};
257             }
258 0 0         return unless $b;
259             #$self->start() unless $dsp;
260             #while (length $b){$b=substr $b,syswrite $dsp,$b};
261 0           $self->data2Device($b);
262             }
263              
264             sub data2Device{
265 0     0 0   my ($self,$data)=@_;
266 0 0         $self->start() unless $dsp;
267 0 0         if ($^O eq 'MSWin32'){
268 0           $dsp->Load($b); # get it
269 0           $dsp->Write(); # hear it
270             }
271             else{
272 0           while (length $data){$data=substr $data,syswrite $dsp,$data};
  0            
273             }
274            
275             }
276              
277             sub playMusic{
278 0     0 0   my ($self,$seriesName,@musicnotes)=@_;
279 0   0       $seriesName//="default";
280 0           foreach(@musicnotes){
281 0 0         ~/P(\d+)/ && sleep("0.2");
282 0 0         ~/\d/ && $self->playSound($seriesName,$_);
283             }
284             }
285             1;