File Coverage

blib/lib/Audio/Nama/Fade.pm
Criterion Covered Total %
statement 32 163 19.6
branch 0 48 0.0
condition 0 12 0.0
subroutine 11 29 37.9
pod 0 16 0.0
total 43 268 16.0


line stmt bran cond sub pod time code
1             # ----------- Fade ------------
2             package Audio::Nama::Fade;
3 1     1   7 use Modern::Perl;
  1         1  
  1         6  
4 1     1   108 use List::Util qw(min);
  1         2  
  1         72  
5             our $VERSION = 1.0;
6 1     1   5 use Carp;
  1         2  
  1         49  
7 1     1   5 use warnings;
  1         2  
  1         27  
8 1     1   12 no warnings qw(uninitialized);
  1         1  
  1         44  
9             our @ISA;
10 1     1   5 use vars qw($n %by_index);
  1         1  
  1         43  
11 1     1   5 use Audio::Nama::Globals qw(:singletons %tn @fade_data);
  1         2  
  1         218  
12 1     1   5 use Audio::Nama::Log qw(logsub logpkg);
  1         2  
  1         54  
13 1     1   6 use Audio::Nama::Effect qw(remove_effect add_effect update_effect);
  1         2  
  1         67  
14             # we don't import 'type' as it would clobber our $fade->type attribute
15 1         7 use Audio::Nama::Object qw(
16             n
17             type
18             mark1
19             mark2
20             duration
21             relation
22             track
23             class
24 1     1   5 );
  1         2  
25             initialize();
26              
27             sub initialize {
28 1     1 0 3 %by_index = ();
29 1         3 @fade_data = (); # for save/restore
30             }
31             sub next_n {
32 0     0 0   my $n = 1;
33 0           while( $by_index{$n} ){ $n++}
  0            
34             $n
35 0           }
36             sub new {
37 0     0 0   my $class = shift;
38 0           my %vals = @_;
39 0 0         croak "undeclared field: @_" if grep{ ! $_is_field{$_} } keys %vals;
  0            
40            
41 0           my $object = bless
42             {
43             # class => $class, # not needed yet
44             n => next_n(),
45             relation => 'fade_from_mark',
46             @_
47             }, $class;
48              
49 0           $by_index{$object->n} = $object;
50              
51 0           logpkg(__FILE__,__LINE__,'debug',"object class: $class, object type: ", ref $object);
52              
53 0           my $id = add_fader($object->track);
54            
55 0           my $track = $tn{$object->track};
56              
57 0           Audio::Nama::request_setup(); # runs apply_fades and reconfigures engine
58 0           $object
59            
60             }
61              
62             # helper routines
63              
64             sub refresh_fade_controller {
65 0     0 0   my $track = shift;
66 0           my @pairs = fader_envelope_pairs($track);
67 0           add_fader($track->name);
68 0           my $operator = Audio::Nama::fxn($track->fader)->type;
69 0           my $off_level = $config->{mute_level}->{$operator};
70 0           my $on_level = $config->{unity_level}->{$operator};
71 0           my @controllers = @{Audio::Nama::fxn($track->fader)->owns};
  0            
72 0           logpkg(__FILE__,__LINE__,'debug',$track->name, ": existing controllers: @controllers");
73 0           for my $controller (@controllers)
74             {
75 0           logpkg(__FILE__,__LINE__,'debug',"removing fade controller $controller");
76 0           remove_effect($controller);
77             }
78              
79             # add controller
80 0           my $reuseid = pop @controllers; # we expect only one
81 0           logpkg(__FILE__,__LINE__,'debug',"applying fade controller");
82 0           add_effect({
83             track => $track,
84             id => $reuseid,
85             parent => $track->fader,
86             type => 'klg', # Ecasound controller
87             params => [ 1, # modify first parameter of fader op
88             $off_level,
89             $on_level,
90             @pairs,
91             ]
92             });
93              
94             # set fader to correct initial value
95             # first fade is type 'in' : 0
96             # first fade is type 'out' : 100%
97            
98 0           update_effect($track->fader,0, initial_level($track->name) * 100)
99             }
100              
101              
102             sub all_fades {
103 0     0 0   my $track_name = shift;
104             sort {
105             $Audio::Nama::Mark::by_name{$a->mark1}->{time} <=> $Audio::Nama::Mark::by_name{$b->mark1}->{time}
106 0           } grep { $_->track eq $track_name } values %by_index
  0            
  0            
107             }
108             sub fades {
109              
110             # get fades within playable region
111            
112 0     0 0   my $track_name = shift;
113 0           my $track = $tn{$track_name};
114 0           my @fades = all_fades($track_name);
115 0 0         return @fades if ! $mode->{offset_run};
116              
117             # handle offset run mode
118 0           my @in_bounds;
119 0           my $play_end = Audio::Nama::play_end_time();
120 0           my $play_start_time = Audio::Nama::play_start_time();
121 0           my $length = $track->wav_length;
122 0           for my $fade (@fades){
123 0 0         my $play_end_time = $play_end ? min($play_end, $length) : $length;
124 0           my $time = $Audio::Nama::Mark::by_name{$fade->mark1}->{time};
125 0 0 0       push @in_bounds, $fade if $time >= $play_start_time and $time <= $play_end_time;
126             }
127             @in_bounds
128 0           }
129              
130             # our envelope must include a straight segment from the
131             # beginning of the track (or region) to the fade
132             # start. Similarly, we need a straight segment
133             # from the last fade to the track (or region) end
134              
135             # - If the first fade is a fade-in, the straight
136             # segment will be at zero-percent level
137             # (otherwise 100%)
138             #
139             # - If the last fade is fade-out, the straight
140             # segment will be at zero-percent level
141             # (otherwise 100%)
142              
143             # although we can get the precise start and endpoints,
144             # I'm using 0 and $track->shifted_playat_time + track length
145              
146             sub initial_level {
147             # return 0, 1 or undef
148             # 0: track starts silent
149             # 1: track starts at full volume
150 0     0 0   my $track_name = shift;
151 0 0         my @fades = fades($track_name) or return undef;
152             # if we fade in we'll hold level zero from beginning
153 0 0 0       (scalar @fades and $fades[0]->type eq 'in') ? 0 : 1
154             }
155             sub exit_level {
156 0     0 0   my $track_name = shift;
157 0 0         my @fades = fades($track_name) or return undef;
158             # if we fade out we'll hold level zero from end
159 0 0 0       (scalar @fades and $fades[-1]->type eq 'out') ? 0 : 1
160             }
161             sub initial_pair { # duration: zero to...
162 0     0 0   my $track_name = shift;
163 0           my $init_level = initial_level($track_name);
164 0 0         defined $init_level or return ();
165 0           (0, $init_level )
166            
167             }
168             sub final_pair { # duration: .... to length
169 0     0 0   my $track_name = shift;
170 0           my $exit_level = exit_level($track_name);
171 0 0         defined $exit_level or return ();
172 0           my $track = $tn{$track_name};
173             (
174 0           $track->shifted_playat_time + $track->wav_length,
175             $exit_level
176             );
177             }
178              
179             sub fader_envelope_pairs {
180             # return number_of_pairs, pos1, val1, pos2, val2,...
181 0     0 0   my $track = shift;
182 0           my @fades = fades($track->name);
183              
184 0           my @specs;
185 0           for my $fade ( @fades ){
186              
187             # calculate fades
188 0           my $marktime1 = Audio::Nama::Mark::mark_time($fade->mark1);
189 0           my $marktime2 = Audio::Nama::Mark::mark_time($fade->mark2);
190 0 0         if ($marktime2) {} # nothing to do
    0          
    0          
191             elsif( $fade->relation eq 'fade_from_mark')
192 0           { $marktime2 = $marktime1 + $fade->duration }
193             elsif( $fade->relation eq 'fade_to_mark')
194             {
195 0           $marktime2 = $marktime1;
196 0           $marktime1 -= $fade->duration
197             }
198 0           else { $fade->dumpp; die "fade processing failed" }
  0            
199 0           logpkg(__FILE__,__LINE__,'debug',"marktime1: $marktime1, marktime2: $marktime2");
200 0           push @specs,
201             [ $marktime1,
202             $marktime2,
203             $fade->type,
204             Audio::Nama::fxn($track->fader)->type,
205             ];
206             }
207             # sort fades - may not need this
208 0           @specs = sort{ $a->[0] <=> $b->[0] } @specs;
  0            
209 0     0     logpkg(__FILE__,__LINE__,'debug',sub{Audio::Nama::json_out( \@specs)});
  0            
210              
211 0           my @pairs = map{ spec_to_pairs($_) } @specs;
  0            
212              
213             # WEIRD message - try to figure this out
214             # XXX results in bug via AUTOLOAD for Edit
215             # @pairs = (initial_pair($track->name), @pairs, final_pair($track->name));
216              
217             # add flat segments
218             # - from start to first fade
219             # - from last fade to end
220              
221              
222             # prepend number of pairs;
223 0 0         unshift @pairs, (scalar @pairs / 2) if @pairs;
224 0           @pairs;
225             }
226            
227             # each 'spec' is an array reference of the form [ $from, $to, $type, $op ]
228             #
229             # $from: time (in seconds)
230             # $to: time (in seconds)
231             # $type: 'in' or 'out'
232             # $op: 'ea' or 'eadb'
233              
234             sub spec_to_pairs {
235 0     0 0   my ($from, $to, $type, $op) = @{$_[0]};
  0            
236 0           logpkg(__FILE__,__LINE__,'debug',"from: $from, to: $to, type: $type");
237 0           my $cutpos;
238             my @pairs;
239              
240             # op 'eadb' uses two-stage fade
241            
242            
243 0 0         if ($op eq 'eadb'){
    0          
244 0 0         if ( $type eq 'out' ){
    0          
245 0           $cutpos = $from + $config->{fade_time1_fraction} * ($to - $from);
246 0           push @pairs, ($from, 1, $cutpos, $config->{fade_down_fraction}, $to, 0);
247             } elsif( $type eq 'in' ){
248 0           $cutpos = $from + $config->{fade_time2_fraction} * ($to - $from);
249 0           push @pairs, ($from, 0, $cutpos, $config->{fade_down_fraction}, $to, 1);
250             }
251             }
252              
253             # op 'ea' uses one-stage fade
254            
255             elsif ($op eq 'ea'){
256 0 0         if ( $type eq 'out' ){
    0          
257 0           push @pairs, ($from, 1, $to, 0);
258             } elsif( $type eq 'in' ){
259 0           push @pairs, ($from, 0, $to, 1);
260             }
261             }
262 0           else { die "missing or illegal fader op: $op" }
263              
264             @pairs
265 0           }
266            
267              
268             # the following routine makes it possible to
269             # remove an edit fade by the name of the edit mark
270            
271             # ???? does it even work?
272             sub remove_by_mark_name {
273 0     0 0   my $mark1 = shift;
274 0           my ($i) = map{ $_->n} grep{ $_->mark1 eq $mark1 } values %by_index;
  0            
  0            
275 0 0         remove($i) if $i;
276             }
277             sub remove_by_index {
278 0     0 0   my $i = shift;
279 0           my $fade = $by_index{$i};
280 0           $fade->remove;
281             }
282              
283             sub remove {
284 0     0 0   my $fade = shift;
285 0           my $track = $tn{$fade->track};
286 0           my $i = $fade->n;
287            
288             # remove object from index
289 0           delete $by_index{$i};
290              
291             # remove fader entirely if this is the last fade on the track
292            
293 0           my @track_fades = all_fades($fade->track);
294 0 0         if ( ! @track_fades ){
295 0           remove_effect($track->fader);
296 0           $tn{$fade->track}->set(fader => undef);
297             }
298 0           else { refresh_fade_controller($track) }
299             }
300             sub add_fader {
301             # if it is missing
302              
303 0     0 0   my $name = shift;
304 0           my $track = $tn{$name};
305              
306 0           my $id = $track->fader;
307              
308             # create a fader if necessary, place before first effect
309             # if it exists
310            
311 0 0 0       if (! $id or ! Audio::Nama::fxn($id)){
312 0           my $first_effect = $track->ops->[0];
313             $id = add_effect({
314             before => $first_effect,
315             track => $track,
316             type => $config->{fader_op},
317 0           params => [0], # XX hardcoded for -ea chain operator
318             });
319 0           $track->set(fader => $id);
320             }
321             $id
322 0           }
323             package Audio::Nama;
324              
325             sub fade_uses_mark {
326 0     0     my $mark_name = shift;
327 0 0         grep{ $_->mark1 eq $mark_name or $_->mark2 eq $mark_name } values %Audio::Nama::Fade::by_index;
  0            
328             }
329            
330             sub apply_fades {
331             # + data from Fade objects residing in %Audio::Nama::Fade::by_name
332             # + apply to tracks
333             # * that are part of current chain setup
334             # * that have a fade operator (i.e. most user tracks)
335 0           map{ Audio::Nama::Fade::refresh_fade_controller($_) }
336 0     0     grep{$_->{fader} }
  0            
337             Audio::Nama::ChainSetup::engine_tracks();
338             }
339            
340              
341             1;