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   1079146 use v5.14;
  8         29  
4 8     8   36 use warnings;
  8         12  
  8         549  
5             our $VERSION = '0.12';
6              
7 8     8   3788 use autodie;
  8         133478  
  8         41  
8 8     8   60291 use Carp;
  8         21  
  8         3951  
9 8     8   5224 use Try::Tiny;
  8         12590  
  8         512  
10 8     8   53 use Scalar::Util qw/blessed/;
  8         13  
  8         338  
11              
12 8     8   4582 use Moo;
  8         58769  
  8         49  
13 8     8   19995 use Types::Standard qw/ArrayRef HashRef GlobRef Str Int Num InstanceOf/;
  8         1098069  
  8         170  
14              
15 8     8   41462 use Audio::TinySoundFont::XS;
  8         24  
  8         627  
16 8     8   6023 use Audio::TinySoundFont::Preset;
  8         32  
  8         335  
17 8     8   4813 use Audio::TinySoundFont::Builder;
  8         31  
  8         14788  
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 1603073 my $class = shift;
69 17         44 my $file = shift;
70 17         96 my $args = Moo::Object::BUILDARGS( $class, @_ );
71              
72 17         161 my $build_fn = $ref_build{ ref $file };
73 17 100       347 croak "Cannot load soundfont file, unknown ref: " . ref($file)
74             if !defined $build_fn;
75 16         66 my $tsf = $build_fn->($file);
76 13         386 $args->{volume} = 0.3;
77              
78 13         40 $args->{_tsf} = $tsf;
79              
80 13         416 return $args;
81             }
82              
83             sub _build_preset_count
84             {
85 12     12   134 my $self = shift;
86              
87 12         382 return $self->_tsf->presetcount;
88             }
89              
90             sub _build_presets
91             {
92 12     12   153 my $self = shift;
93              
94 12         23 my %result;
95 12         278 foreach my $i ( 0 .. $self->preset_count )
96             {
97 36   100     24642 my $name = $self->_tsf->get_presetname($i) // '';
98 36         74 my $n = '';
99 36         62 my $conflict = 1;
100 36         137 while ( exists $result{"$name$n"} )
101             {
102 12         31 $conflict++;
103 12         51 $n = "_$conflict";
104             }
105 36         134 $name = "$name$n";
106 36         960 $result{$name} = Audio::TinySoundFont::Preset->new(
107             soundfont => $self,
108             index => $i,
109             );
110             }
111              
112 12         1022 return \%result;
113             }
114              
115             sub preset
116             {
117 64     64 1 6835 my $self = shift;
118 64         107 my $name = shift;
119              
120 64         1909 my $preset = $self->presets->{$name};
121              
122 64 100       1149 croak qq{Could not find preset "$name"}
123             if !defined $preset;
124              
125 63         152 return $preset;
126             }
127              
128             sub preset_index
129             {
130 4     4 1 4705 my $self = shift;
131 4         9 my $index = shift;
132              
133 4 100       145 croak qq{Could not find preset "$index"}
134             if $index >= $self->preset_count;
135              
136 2         71 return Audio::TinySoundFont::Preset->new(
137             soundfont => $self,
138             index => $index,
139             );
140             }
141              
142             sub new_builder
143             {
144 8     8 1 8371 my $self = shift;
145 8         20 my @script = @_;
146              
147 8 100 100     61 if ( @script == 1 && ref $script[0] eq 'ARRAY' )
148             {
149 5         10 @script = @{ $script[0] };
  5         13  
150             }
151              
152 8         164 return Audio::TinySoundFont::Builder->new(
153             soundfont => $self,
154             play_script => \@script,
155             );
156             }
157              
158             sub active_voices
159             {
160 6     6 1 2520 my $self = shift;
161 6         103 return $self->_tsf->active_voices;
162             }
163              
164             sub is_active
165             {
166 16     16 1 269 my $self = shift;
167 16         474 return !!$self->_tsf->active_voices;
168             }
169              
170             sub note_on
171             {
172 31     31 1 2433 my $self = shift;
173 31   66     322 my $preset = shift // croak "Preset is required for note_on";
174 30   100     108 my $note = shift // 60;
175 30   100     130 my $vel = shift // 0.5;
176              
177 30 100       94 if ( !blessed $preset )
178             {
179 29         95 $preset = $self->preset($preset);
180             }
181              
182 30         154 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
183              
184 29         46983 $self->_tsf->note_on( $preset->index, $note, $vel );
185 29         3985 return;
186             }
187              
188             sub note_off
189             {
190 29     29 1 28411 my $self = shift;
191 29   66     286 my $preset = shift // croak "Preset is required for note_off";
192 28   100     98 my $note = shift // 60;
193              
194 28 100       83 if ( !blessed $preset )
195             {
196 27         82 $preset = $self->preset($preset);
197             }
198              
199 28         131 ( InstanceOf ['Audio::TinySoundFont::Preset'] )->($preset);
200              
201 27         47789 $self->_tsf->note_off( $preset->index, $note );
202 27         3362 return;
203             }
204              
205             sub render
206             {
207 14     14 1 71 my $self = shift;
208 14         39 my %args = @_;
209              
210 14         31 my $tsf = $self->_tsf;
211              
212 14         57 my $SR = $tsf->SAMPLE_RATE;
213 14   100     65 my $seconds = $args{seconds} // 0;
214 14   66     93 my $samples = ( $seconds * $SR ) || $args{samples} // $SR;
      66        
215              
216 14         14793 return $tsf->render($samples);
217             }
218              
219             sub render_unpack
220             {
221 1     1 1 4 my $self = shift;
222              
223 1         4 return unpack( 's<*', $self->render(@_) );
224             }
225              
226             sub db_to_vol
227             {
228 28     28 1 64 my $self = shift;
229 28         77 my $db = shift;
230              
231             return
232 28 100       134 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       36 $db
    100          
236             = $db > 0 ? 0
237             : $db < -100 ? -100
238             : $db;
239 7         44 return 10**( $db / 20 );
240             }
241              
242             1;
243             __END__