File Coverage

blib/lib/Audio/TinySoundFont.pm
Criterion Covered Total %
statement 105 105 100.0
branch 18 18 100.0
condition 21 25 84.0
subroutine 24 24 100.0
pod 10 11 90.9
total 178 183 97.2


line stmt bran cond sub pod time code
1             package Audio::TinySoundFont;
2              
3 8     8   512715 use v5.14;
  8         103  
4 8     8   44 use warnings;
  8         16  
  8         385  
5             our $VERSION = '0.10';
6              
7 8     8   4176 use autodie;
  8         129822  
  8         36  
8 8     8   55079 use Carp;
  8         17  
  8         536  
9 8     8   3664 use Try::Tiny;
  8         12901  
  8         495  
10 8     8   59 use Scalar::Util qw/blessed/;
  8         21  
  8         376  
11              
12 8     8   5768 use Moo;
  8         89442  
  8         42  
13 8     8   18468 use Types::Standard qw/ArrayRef HashRef GlobRef Str Int Num InstanceOf/;
  8         624736  
  8         94  
14              
15 8     8   16491 use Audio::TinySoundFont::XS;
  8         21  
  8         390  
16 8     8   3384 use Audio::TinySoundFont::Preset;
  8         28  
  8         296  
17 8     8   4008 use Audio::TinySoundFont::Builder;
  8         22  
  8         12377  
18              
19             has _tsf => (
20             is => 'ro',
21             isa => InstanceOf ['Audio::TinySoundFont::XS'],
22             required => 1,
23             );
24              
25             has volume => (
26             is => 'rw',
27             isa => Num,
28             default => 0.3,
29             trigger => sub { my $self = shift; $self->_tsf->set_volume(shift) },
30             );
31              
32             has preset_count => (
33             is => 'lazy',
34             isa => Int,
35             );
36              
37             has presets => (
38             is => 'lazy',
39             isa => HashRef,
40             );
41              
42             *SAMPLE_RATE = \&Audio::TinySoundFont::XS::SAMPLE_RATE;
43              
44             my $XS = 'Audio::TinySoundFont::XS';
45             my %ref_build = (
46             '' => sub
47             {
48             my $file = shift;
49             croak qq{File "$file" doesn't exist}
50             if !-e $file;
51             return try { $XS->load_file($file) } catch { croak $_ };
52             },
53             SCALAR => sub
54             {
55             my $str = shift;
56             open my $glob, '<', $str;
57             return try { $XS->load_fh($glob) } catch { croak $_ };
58             },
59             GLOB => sub
60             {
61             my $fh = shift;
62             return try { $XS->load_fh($fh) } catch { croak $_ };
63             },
64             );
65              
66             sub BUILDARGS
67             {
68 17     17 0 45621 my $class = shift;
69 17         72 my $file = shift;
70 17         77 my $args = Moo::Object::BUILDARGS( $class, @_ );
71              
72 17         157 my $build_fn = $ref_build{ ref $file };
73 17 100       283 croak "Cannot load soundfont file, unknown ref: " . ref($file)
74             if !defined $build_fn;
75 16         56 my $tsf = $build_fn->($file);
76 13         424 $args->{volume} = 0.3;
77              
78 13         39 $args->{_tsf} = $tsf;
79              
80 13         322 return $args;
81             }
82              
83             sub _build_preset_count
84             {
85 12     12   138 my $self = shift;
86              
87 12         241 return $self->_tsf->presetcount;
88             }
89              
90             sub _build_presets
91             {
92 12     12   152 my $self = shift;
93              
94 12         24 my %result;
95 12         234 foreach my $i ( 0 .. $self->preset_count )
96             {
97 36   100     20222 my $name = $self->_tsf->get_presetname($i) // '';
98 36         68 my $n = '';
99 36         60 my $conflict = 1;
100 36         125 while ( exists $result{"$name$n"} )
101             {
102 12         25 $conflict++;
103 12         44 $n = "_$conflict";
104             }
105 36         65 $name = "$name$n";
106 36         618 $result{$name} = Audio::TinySoundFont::Preset->new(
107             soundfont => $self,
108             index => $i,
109             );
110             }
111              
112 12         837 return \%result;
113             }
114              
115             sub preset
116             {
117 64     64 1 5008 my $self = shift;
118 64         105 my $name = shift;
119              
120 64         1525 my $preset = $self->presets->{$name};
121              
122 64 100       977 croak qq{Could not find preset "$name"}
123             if !defined $preset;
124              
125 63         160 return $preset;
126             }
127              
128             sub preset_index
129             {
130 4     4 1 2880 my $self = shift;
131 4         8 my $index = shift;
132              
133 4 100       95 croak qq{Could not find preset "$index"}
134             if $index >= $self->preset_count;
135              
136 2         56 return Audio::TinySoundFont::Preset->new(
137             soundfont => $self,
138             index => $index,
139             );
140             }
141              
142             sub new_builder
143             {
144 8     8 1 6462 my $self = shift;
145 8         24 my @script = @_;
146              
147 8 100 100     51 if ( @script == 1 && ref $script[0] eq 'ARRAY' )
148             {
149 5         10 @script = @{ $script[0] };
  5         16  
150             }
151              
152 8         166 return Audio::TinySoundFont::Builder->new(
153             soundfont => $self,
154             play_script => \@script,
155             );
156             }
157              
158             sub active_voices
159             {
160 6     6 1 2513 my $self = shift;
161 6         68 return $self->_tsf->active_voices;
162             }
163              
164             sub is_active
165             {
166 16     16 1 201 my $self = shift;
167 16         327 return !!$self->_tsf->active_voices;
168             }
169              
170             sub note_on
171             {
172 31     31 1 1560 my $self = shift;
173 31   66     284 my $preset = shift // croak "Preset is required for note_on";
174 30   100     104 my $note = shift // 60;
175 30   100     96 my $vel = shift // 0.5;
176              
177 30 100       102 if ( !blessed $preset )
178             {
179 29         97 $preset = $self->preset($preset);
180             }
181              
182 30         136 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
183              
184 29         37078 $self->_tsf->note_on( $preset->index, $note, $vel );
185 29         2984 return;
186             }
187              
188             sub note_off
189             {
190 29     29 1 21137 my $self = shift;
191 29   66     210 my $preset = shift // croak "Preset is required for note_off";
192 28   100     97 my $note = shift // 60;
193              
194 28 100       101 if ( !blessed $preset )
195             {
196 27         73 $preset = $self->preset($preset);
197             }
198              
199 28         131 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
200              
201 27         31105 $self->_tsf->note_off( $preset->index, $note );
202 27         2687 return;
203             }
204              
205             sub render
206             {
207 14     14 1 79 my $self = shift;
208 14         36 my %args = @_;
209              
210 14         34 my $tsf = $self->_tsf;
211              
212 14         44 my $SR = $tsf->SAMPLE_RATE;
213 14   100     68 my $seconds = $args{seconds} // 0;
214 14   66     66 my $samples = ( $seconds * $SR ) || $args{samples} // $SR;
      66        
215              
216 14         14216 return $tsf->render($samples);
217             }
218              
219             sub render_unpack
220             {
221 1     1 1 5 my $self = shift;
222              
223 1         19 return unpack( 's<*', $self->render(@_) );
224             }
225              
226             sub db_to_vol
227             {
228 28     28 1 83 my $self = shift;
229 28         55 my $db = shift;
230              
231             return
232 28 100       142 if !defined $db;
233              
234             # Volume is a float 0.0-1.0, db is in dB -100..0, so adjust it to a float
235 7 100       50 $db
    100          
236             = $db > 0 ? 0
237             : $db < -100 ? -100
238             : $db;
239 7         111 return 10**( $db / 20 );
240             }
241              
242             1;
243             __END__