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   577884 use v5.14;
  8         97  
4 8     8   43 use warnings;
  8         16  
  8         360  
5             our $VERSION = '0.11';
6              
7 8     8   4078 use autodie;
  8         129763  
  8         37  
8 8     8   54346 use Carp;
  8         18  
  8         586  
9 8     8   3769 use Try::Tiny;
  8         12741  
  8         479  
10 8     8   57 use Scalar::Util qw/blessed/;
  8         17  
  8         375  
11              
12 8     8   4799 use Moo;
  8         86840  
  8         96  
13 8     8   18334 use Types::Standard qw/ArrayRef HashRef GlobRef Str Int Num InstanceOf/;
  8         615046  
  8         100  
14              
15 8     8   16248 use Audio::TinySoundFont::XS;
  8         25  
  8         429  
16 8     8   3339 use Audio::TinySoundFont::Preset;
  8         34  
  8         294  
17 8     8   4156 use Audio::TinySoundFont::Builder;
  8         20  
  8         12290  
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 47165 my $class = shift;
69 17         42 my $file = shift;
70 17         76 my $args = Moo::Object::BUILDARGS( $class, @_ );
71              
72 17         157 my $build_fn = $ref_build{ ref $file };
73 17 100       290 croak "Cannot load soundfont file, unknown ref: " . ref($file)
74             if !defined $build_fn;
75 16         52 my $tsf = $build_fn->($file);
76 13         360 $args->{volume} = 0.3;
77              
78 13         42 $args->{_tsf} = $tsf;
79              
80 13         330 return $args;
81             }
82              
83             sub _build_preset_count
84             {
85 12     12   129 my $self = shift;
86              
87 12         247 return $self->_tsf->presetcount;
88             }
89              
90             sub _build_presets
91             {
92 12     12   143 my $self = shift;
93              
94 12         27 my %result;
95 12         239 foreach my $i ( 0 .. $self->preset_count )
96             {
97 36   100     19569 my $name = $self->_tsf->get_presetname($i) // '';
98 36         73 my $n = '';
99 36         55 my $conflict = 1;
100 36         118 while ( exists $result{"$name$n"} )
101             {
102 12         31 $conflict++;
103 12         41 $n = "_$conflict";
104             }
105 36         56 $name = "$name$n";
106 36         638 $result{$name} = Audio::TinySoundFont::Preset->new(
107             soundfont => $self,
108             index => $i,
109             );
110             }
111              
112 12         885 return \%result;
113             }
114              
115             sub preset
116             {
117 64     64 1 4950 my $self = shift;
118 64         143 my $name = shift;
119              
120 64         1556 my $preset = $self->presets->{$name};
121              
122 64 100       1014 croak qq{Could not find preset "$name"}
123             if !defined $preset;
124              
125 63         144 return $preset;
126             }
127              
128             sub preset_index
129             {
130 4     4 1 2989 my $self = shift;
131 4         7 my $index = shift;
132              
133 4 100       99 croak qq{Could not find preset "$index"}
134             if $index >= $self->preset_count;
135              
136 2         59 return Audio::TinySoundFont::Preset->new(
137             soundfont => $self,
138             index => $index,
139             );
140             }
141              
142             sub new_builder
143             {
144 8     8 1 6187 my $self = shift;
145 8         23 my @script = @_;
146              
147 8 100 100     52 if ( @script == 1 && ref $script[0] eq 'ARRAY' )
148             {
149 5         12 @script = @{ $script[0] };
  5         15  
150             }
151              
152 8         210 return Audio::TinySoundFont::Builder->new(
153             soundfont => $self,
154             play_script => \@script,
155             );
156             }
157              
158             sub active_voices
159             {
160 6     6 1 2501 my $self = shift;
161 6         58 return $self->_tsf->active_voices;
162             }
163              
164             sub is_active
165             {
166 16     16 1 187 my $self = shift;
167 16         330 return !!$self->_tsf->active_voices;
168             }
169              
170             sub note_on
171             {
172 31     31 1 1627 my $self = shift;
173 31   66     287 my $preset = shift // croak "Preset is required for note_on";
174 30   100     107 my $note = shift // 60;
175 30   100     94 my $vel = shift // 0.5;
176              
177 30 100       106 if ( !blessed $preset )
178             {
179 29         100 $preset = $self->preset($preset);
180             }
181              
182 30         138 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
183              
184 29         36911 $self->_tsf->note_on( $preset->index, $note, $vel );
185 29         3010 return;
186             }
187              
188             sub note_off
189             {
190 29     29 1 18805 my $self = shift;
191 29   66     228 my $preset = shift // croak "Preset is required for note_off";
192 28   100     99 my $note = shift // 60;
193              
194 28 100       107 if ( !blessed $preset )
195             {
196 27         80 $preset = $self->preset($preset);
197             }
198              
199 28         113 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
200              
201 27         30329 $self->_tsf->note_off( $preset->index, $note );
202 27         2624 return;
203             }
204              
205             sub render
206             {
207 14     14 1 81 my $self = shift;
208 14         35 my %args = @_;
209              
210 14         33 my $tsf = $self->_tsf;
211              
212 14         46 my $SR = $tsf->SAMPLE_RATE;
213 14   100     63 my $seconds = $args{seconds} // 0;
214 14   66     78 my $samples = ( $seconds * $SR ) || $args{samples} // $SR;
      66        
215              
216 14         13408 return $tsf->render($samples);
217             }
218              
219             sub render_unpack
220             {
221 1     1 1 2 my $self = shift;
222              
223 1         29 return unpack( 's<*', $self->render(@_) );
224             }
225              
226             sub db_to_vol
227             {
228 28     28 1 85 my $self = shift;
229 28         64 my $db = shift;
230              
231             return
232 28 100       124 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       28 $db
    100          
236             = $db > 0 ? 0
237             : $db < -100 ? -100
238             : $db;
239 7         39 return 10**( $db / 20 );
240             }
241              
242             1;
243             __END__