=head1 NAME
Term::Graille::Audio
Modal hierarchical Menu system
=head1 SYNOPSIS
use Term::Graille::Audio; # TERM::Graille's Audio module
my $beep=Term::Graille::Audio->new();# create object;
$beep->playSound(undef, "A#1"); # use built-in samples to play a note
=head1 DESCRIPTION
Developed to use Audio in Braille Applications. Again the empahsis
to try and avoid external libraries. It does neeed some things to connect
with sound hardware though. Linux systems need pulseaudio utilities
Winodws is as yet untested. Windows systems need Win32::Sound
=begin html
=end html
=head1 FUNCTIONS
=cut
package Term::Graille::Audio;
use strict; use warnings;
use IO::File;
use Time::HiRes ("sleep"); # allow fractional sleeps
use if $^O eq 'MSWin32', "Win32::Sound";
use Storable;
use utf8;
our $VERSION= 0.01;
our $dsp;
=head3 Cnew(%params)>
Creates a new audio interface object; params are
C samples stored in external files may be loaded allowing
different sounds to be played back. These are stored and retrieved
as Storable files. This is optional, an if not supplied,
Term::Graille::Audio gnerates its own sinwave sample.
=cut
sub new{
my ($class,%params)=@_;
my $self={};
our $dsp;
bless $self, $class;
$self->{samples}={};
if ($params{samples}){
my @files=ref $params{samples}?@{$params{samples}}:($params{samples});
foreach my $file (@files){
my $name=$file; $name =~ s{^.*[/\\]|\.[^\.]+$}{}g;
$self->{samples}->{$name}=retrieve($file) if (-e $file)
}
}
else{
$self->makeSampleSet();
};
return $self;
}
sub start{
my $self=shift;
if ($^O eq 'MSWin32'){
our $dsp = new Win32::Sound::WaveOut(8000, 8, 1);
}
else{
open(our $dsp,"|padsp tee /dev/dsp > /dev/null") or warn "DSP can not be intiated $!";
}
}
sub makeSampleSet{ # generates full from C0 t0 B8 (96 notes)
my ($self,$name, $middleA, $sps,$type)=@_;
$name//="default";
$middleA//=419;
$sps//=1024;
my @octaves=("C","C#","D","D#","E","F","F#","G","G#","A","A#","B") x 8; # create 96 notes
my @keys= map{$octaves[$_].(int ($_/12)) }(0..$#octaves); # append the octave number
$self->{samples}->{$name}={keys=>\@keys,sps=>$sps,middleA=>$middleA};
for my $k(1..scalar @keys){
$self->{samples}->{$name}->{$keys[$k-1]}=makeNotes($self,$middleA,$sps,$k-58,$type);
}
}
sub record{
my ($self,$sps)=@_;
my ($buffer,$recording);
open (my $rec, "< :raw :bytes","|padsp /dev/dsp") or die "Could not open for recording $!";
binmode($rec);
$recording.=$buffer while (read($rec, $buffer, $sps));
close $rec;
return $recording;
}
#A term::Graille Specific Piano Keyboard can be setup and drawn
sub setupKeyboard{
my ($self,$canvas,$params)=@_;
$self->{canvas}=$canvas;
$self->{keyboard}={};
my %default;
@default{qw/top left vSep hSep/}=(55,20,4,8);
foreach (qw/top left vSep hSep/){
$self->{keyboard}->{$_}=$params->{$_}//$default{$_};
};
$self->{keys}={};
no warnings qw{qw};
# my ($top,$left,$vSep,$hSep)=(55,20,4,8);
my @keyNotes=([[qw/1 2 3 4 5 6 7 8 9 0 - = /],
[qw/. C# D# . F# G# A# . C# D# . F#/]],
[[qw/q w e r t y u i o p [/],
[qw/C D E F G A B C D E F/]],
[[qw/a s d f g h j k l ; /],
[qw/G# A# . C# D# . F# G# A# . /]],
[[qw{\ z x c v b n m , . /}],
[qw/G A B C D E F G A B C/]],
);
my @rowShift=(0,4,8,4);
for my $keyRow (0..$#keyNotes){
my @keys=@{$keyNotes[$keyRow]->[0]};
my @notes=@{$keyNotes[$keyRow]->[1]};
foreach my $keyPos(0..$#keys){
$self->{keys}->{$keys[$keyPos]}={
x=>$self->{keyboard}->{hSep}*$keyPos+$self->{keyboard}->{left}+$rowShift[$keyRow],
y=>$self->{keyboard}->{top}-3*$self->{keyboard}->{vSep}*$keyRow,
n=>$notes[$keyPos],
c=>$keyRow%2?"black on_white":"white on_black",
} unless $notes[$keyPos] eq ".";
}
}
};
sub drawKeyboard{
my ($self)=@_;
for my $key (keys %{$self->{keys}}){
$self->drawKey($key)
}
}
sub drawKey{
my ($self,$key,$colour)=@_;
return unless $key && defined $self->{keys}->{$key};
my ($x,$y,$n,$c)=@{$self->{keys}->{$key}}{qw/x y n c/};
$c=$colour if $colour;
$self->{canvas}->textAt($x,$y,$key,$c);
$self->{canvas}->textAt($x+4,$y," ","reset");
$self->{canvas}->textAt($x,$y-4,$n,$c) ;
$self->{canvas}->textAt($x+4,$y-4," ","reset");
}
sub makeKeyboard{
my ($self)=@_;
my $keyboard=<"C#3", 3=>"D#3", 5=>"F#3", 6=>"G#3", 7=>"A#3", 9=>"C#4", "0"=>"D#4", "="=>"F#4",
q=>"C3", w=>"D3", e=>"E3", r=>"F3", t=>"G3", y=>"A3", u=>"B3", i=>"C4", o=>"D4", p=>"E4", "["=>"F4",
s=>"G#4", d=>"A#4", f=>"C#5", g=>"D#5", j=>"F#5", k=>"G#5", l=>"A#5", "'"=>"C#6",
"\\"=>"G4", z=>"A4", x=>"B4", c=>"C5", v=> "D5", b=>"E5", n=>"F5", m=>"G5", ","=>"A5", "."=>"B5", "/"=>"C6",
};
return ($keyboard,$key2note);
}
=head3 CsaveSampleSet($name,$path)>
Saves a sample set to file to directory (adds an extenison ".spl)
=cut
sub saveSampleSet{
my ($self,$name,$directory)=@_;
store($self->{samples}->{name}, $directory."/".$name.".spl");
}
=head3 CsaveSampleSet($name,$path)>
loads a sample set from full path, excludes the extension in the name;
=cut
sub loadSampleSet{
my ($self,$file)=@_;
my $name=$file; $name =~ s{^.*[/\\]|\.[^\.]+$}{}g;
$self->{samples}->{$name}=retrieve($file) if (-e $file)
}
sub makeNotes{
my ($self, $middleA, $sps, $offset, $type)=@_;
$offset//=0;
my $f=$middleA*2**($offset/12);
my $s=pack'C*',map 127*(1+sin(($_*2*3.14159267*$f)/$sps)),0..$sps-1; # generate the sample
return {f=>sprintf("%.2f",$f),s=>$s};
}
=head3 CplaySound($name,$soundName)>
Plays a note from the samplesset ($name). If sampleset is undefined then
the builtin-"default" sample set is used.
=cut
sub playSound{
my ($self, $seriesName, $soundName)=@_;
$seriesName//="default";
my $b;
if (exists $self->{samples}->{$seriesName}){
$b=$soundName=~/^\d/?
$self->{samples}->{$seriesName}->{$self->{samples}->{$seriesName}->{keys}->[$soundName]}->{s}:
$self->{samples}->{$seriesName}->{$soundName}->{s};
}
elsif (exists $self->{sounds}->{$seriesName}){
$b=$soundName=~/^\d/?
$self->{samples}->{$seriesName}->{$self->{samples}->{$seriesName}->{items}->[$soundName]}->{s}:
$b= $self->{sounds}->{$seriesName}->{$soundName}->{s};
}
return unless $b;
#$self->start() unless $dsp;
#while (length $b){$b=substr $b,syswrite $dsp,$b};
$self->data2Device($b);
}
sub data2Device{
my ($self,$data)=@_;
$self->start() unless $dsp;
if ($^O eq 'MSWin32'){
$dsp->Load($b); # get it
$dsp->Write(); # hear it
}
else{
while (length $data){$data=substr $data,syswrite $dsp,$data};
}
}
sub playMusic{
my ($self,$seriesName,@musicnotes)=@_;
$seriesName//="default";
foreach(@musicnotes){
~/P(\d+)/ && sleep("0.2");
~/\d/ && $self->playSound($seriesName,$_);
}
}
1;