File Coverage

blib/lib/Audio/Nama/Effect.pm
Criterion Covered Total %
statement 55 658 8.3
branch 0 194 0.0
condition 0 61 0.0
subroutine 16 97 16.4
pod 0 66 0.0
total 71 1076 6.6


line stmt bran cond sub pod time code
1             {
2             package Audio::Nama::Effect;
3 1     1   4 use Modern::Perl;
  1         3  
  1         6  
4 1     1   973 use List::MoreUtils qw(first_index insert_after_string);
  1         11961  
  1         7  
5 1     1   653 use Carp qw(carp cluck croak confess);
  1         2  
  1         62  
6 1     1   5 use Data::Dumper::Concise;
  1         3  
  1         55  
7 1     1   5 use Audio::Nama::Assign qw(json_out);
  1         1  
  1         47  
8 1     1   5 use Audio::Nama::Log qw(logsub logpkg);
  1         2  
  1         54  
9 1         169 use Audio::Nama::Globals qw(
10             $fx
11             $fx_cache
12             $ui
13             %ti
14             %tn
15             %bn
16             %en
17             $config
18             $setup
19             $project
20             $this_engine
21 1     1   5 $this_track);
  1         2  
22              
23              
24 1         8 use Audio::Nama::Object qw(
25             id
26             type
27             chain
28             class
29             params
30             params_log
31             display
32             parent
33             owns
34             bypassed
35             name
36             surname
37              
38 1     1   6 );
  1         2  
39             *this_op = \&Audio::Nama::this_op;
40             *this_param = \&Audio::Nama::this_param;
41             *this_stepsize = \&Audio::Nama::this_stepsize;
42             our %by_id;
43             our $AUTOLOAD;
44             import_engine_subs();
45              
46             sub initialize {
47              
48 0     0 0 0 %by_id = () ;
49            
50             # effect variables - no object code (yet)
51 0         0 $fx->{id_counter} = "A"; # autoincrement counter
52              
53             # volume settings
54 0         0 $fx->{muted} = [];
55             }
56             sub AUTOLOAD {
57 0     0   0 my $self = shift;
58             #say "got self: $self", Audio::Nama::Dumper $self;
59 0 0       0 die 'not object' unless ref $self;
60             # get tail of method call
61 0         0 my ($call) = $AUTOLOAD =~ /([^:]+)$/;
62             # see if this can be satisfied by a field from
63             # the corresponding effects registry entry
64 0 0       0 $call = 'name' if $call eq 'fxname';
65 0         0 $self->about->{$call}
66             }
67       0     sub DESTROY {}
68              
69             sub new {
70 0     0 0 0 my ($class, %args) = @_;
71              
72 0         0 my $is_restore = $args{restore};
73              
74             # remove arguments that won't be part of object
75 0         0 delete $args{$_} for qw(restore before);
76            
77 0         0 my $self;
78              
79 0         0 my $id = $args{id};
80              
81             # return an existing object that has this ID
82             # Why do this instead of throw an exception?
83 0 0 0     0 if ($id and $self = fxn($id)){
84 0         0 logpkg(__FILE__,__LINE__,'debug',"$id: returning existing object, constructor arguments ignored");
85 0         0 return $self
86             }
87              
88 0         0 my $i = effect_index($args{type});
89 0 0       0 defined $i or confess "$args{type}: plugin not found.";
90              
91             # allocate effect ID if we don't have one yet
92             #
93 0 0       0 my $how_allocated = $id ? 'recycled' : 'issued';
94 0   0     0 $id //= new_effect_id();
95 0         0 logpkg(__FILE__,__LINE__,'debug',"$id: effect ID $how_allocated for track $args{chain}");
96            
97 0         0 $args{id} = $id;
98 0   0     0 $args{display} //= $fx_cache->{registry}->[$i]->{display};
99 0   0     0 $args{owns} //= [];
100              
101 0         0 my $track = $ti{$args{chain}};
102              
103 0         0 my $parent_id = $args{parent};
104              
105             # set defaults for effects without values provided
106             # but skip controllers
107            
108             # append_effect() also assigns defaults, so why not
109             # do all the assigning here?
110            
111 0 0 0     0 if (! $parent_id and ! $args{params}){
112 0         0 my @vals;
113 0         0 logpkg(__FILE__,__LINE__,'debug', "no settings found, loading defaults if present");
114            
115             # if the effect is a controller (has a parent), we don't
116             # initialize the first parameter (the control target)
117            
118 0         0 for my $j (0..$fx_cache->{registry}->[$i]->{count} - 1) {
119            
120 0         0 push @vals, $fx_cache->{registry}->[$i]->{params}->[$j]->{default};
121             }
122 0         0 logpkg(__FILE__,__LINE__,'debug', "copid: $id defaults: @vals");
123 0         0 $args{params} = \@vals;
124             }
125              
126 0         0 logpkg(__FILE__,__LINE__,'debug', "effect args: ",Dumper \%args);
127            
128 0         0 $self = bless \%args, $class;
129 0         0 $by_id{$self->id} = $self;
130            
131 0 0       0 return $self if $is_restore;
132              
133 0 0       0 if ($parent_id) {
134 0         0 logpkg(__FILE__,__LINE__,'debug', "parent found: $parent_id");
135              
136             # store relationship
137              
138 0         0 my $parent = fxn($parent_id);
139 0         0 my $owns = $parent->owns;
140 0         0 logpkg(__FILE__,__LINE__,'debug',"parent owns @$owns");
141              
142             # register effect_id with parent unless it is already there
143 0 0       0 push @$owns, $id unless grep { $id eq $_ } @$owns;
  0         0  
144              
145 0     0   0 logpkg(__FILE__,__LINE__,'debug',sub{join " ", "my attributes:", json_out($self->as_hash)});
  0         0  
146             # find position of parent id in the track ops array
147             # and insert child id immediately afterwards
148             # unless already present
149              
150 0         0 insert_after_string($parent_id, $id, @{$track->ops})
151 0 0       0 unless grep {$id eq $_} @{$track->ops}
  0         0  
  0         0  
152             }
153             else {
154              
155             # append effect_id to track list unless already present
156 0 0       0 push @{$track->ops}, $id unless grep {$id eq $_} @{$track->ops}
  0         0  
  0         0  
  0         0  
157             }
158 0         0 $self
159             }
160              
161             # fx method delivers hash, previously via $fx->{ applied}->{$id}
162             # TODO: get rid of this entirely
163 0     0 0 0 sub fx { my $self = shift; $self }
  0         0  
164              
165             # provide object
166             {
167 1     1   6 no warnings 'redefine';
  1         1  
  1         105  
168             sub parent {
169 0     0 0 0 my $self = shift;
170 0         0 fxn($self->{parent});
171             }
172             }
173              
174             sub is_read_only {
175 0     0 0 0 my ($self, $param) = @_;
176 1     1   5 no warnings 'uninitialized';
  1         2  
  1         203  
177 0         0 $self->about->{params}->[$param]->{dir} eq 'output'
178             }
179 0     0 0 0 sub remove_name { my $self = shift; delete $self->{name} }
  0         0  
180 0     0 0 0 sub set_name { my $self = shift; $self->{name} = shift }
  0         0  
181 0     0 0 0 sub set_surname { my $self = shift; $self->{surname} = shift}
  0         0  
182 0     0 0 0 sub is_controller { my $self = shift; $self->parent }
  0         0  
183              
184             sub has_read_only_param {
185 0     0 0 0 my $self = shift;
186 1     1   5 no warnings 'uninitialized';
  1         2  
  1         671  
187 0         0 my $entry = $self->about;
188 0         0 for(0..scalar @{$entry->{params}} - 1)
  0         0  
189             {
190 0 0       0 return 1 if $entry->{params}->[$_]->{dir} eq 'output'
191             }
192             }
193              
194             sub registry_index {
195 0     0 0 0 my $self = shift;
196 0         0 $fx_cache->{full_label_to_index}->{ $self->type };
197             }
198             sub ecasound_controller_index {
199 0     0 0 0 logsub("&ecasound_controller_index");
200 0         0 my $self = shift;
201 0         0 my $id = $self->id;
202 0         0 my $chain = $self->chain;
203 0         0 my $track = $ti{$chain};
204 0         0 my @ops = @{$track->ops};
  0         0  
205 0         0 my $operator_count = 0;
206 0         0 my $position;
207 0         0 for my $i (0..scalar @ops - 1) {
208 0 0       0 $position = $i, last if $ops[$i] eq $id;
209 0 0       0 $operator_count++ if ! Audio::Nama::fxn($ops[$i])->is_controller;
210             }
211 0         0 $position -= $operator_count; # skip operators
212 0         0 ++$position; # translates 0th to chain-position 1
213             }
214             sub ecasound_operator_index { # does not include offset
215 0     0 0 0 logsub("&ecasound_operator_index");
216 0         0 my $self = shift;
217 0         0 my $id = $self->id;
218 0         0 my $chain = $self->chain;
219 0         0 my $track = $ti{$chain};
220 0         0 my @ops = @{$track->ops};
  0         0  
221 0         0 my $controller_count = 0;
222 0         0 my $position;
223 0         0 for my $i (0..scalar @ops - 1) {
224 0 0       0 $position = $i, last if $ops[$i] eq $id;
225 0 0       0 $controller_count++ if Audio::Nama::fxn($ops[$i])->is_controller;
226             }
227 0         0 $position -= $controller_count; # skip controllers
228 0         0 ++$position; # translates 0th to chain-position 1
229             }
230             sub ecasound_effect_index {
231 0     0 0 0 logsub("&ecasound_effect_index");
232 0         0 my $self = shift;
233 0         0 my $n = $self->chain;
234 0         0 my $id = $self->id;
235 0         0 my $opcount = 0;
236 0         0 logpkg(__FILE__,__LINE__,'debug', "id: $id, n: $n, ops: @{ $ti{$n}->ops }" );
  0         0  
237 0         0 for my $op (@{ $ti{$n}->ops }) {
  0         0  
238             # increment only for ops, not controllers
239 0 0       0 next if $self->is_controller;
240 0         0 ++$opcount; # first index is 1
241 0 0       0 last if $op eq $id
242             }
243 1     1   5 no warnings 'uninitialized';
  1         3  
  1         636  
244 0         0 $self->offset + $opcount;
245             }
246             sub track_effect_index { # the position of the ID in the track's op array
247 0     0 0 0 my $self = shift;
248 0         0 my $id = $self->id;
249 0     0   0 my $pos = first_index {$id eq $_} @{$self->track->ops} ;
  0         0  
  0         0  
250 0         0 $pos
251             }
252             sub sync_one_effect {
253 0     0 0 0 my $self= shift;
254 0         0 my $chain = $self->chain;
255 0         0 Audio::Nama::eval_iam("c-select $chain");
256 0         0 Audio::Nama::eval_iam("cop-select " .( $self->offset + $self->ecasound_operator_index ) );
257 0         0 $self->set(params => get_ecasound_cop_params( scalar @{$self->params} ));
  0         0  
258             }
259             sub offset {
260 0     0 0 0 my $self = shift;
261 0         0 $fx->{offset}->{$self->chain}
262             }
263             sub root_parent {
264 0     0 0 0 my $self = shift;
265 0         0 my $parent = $self->parent;
266 0 0 0     0 $parent and $parent->parent or $parent or $self
      0        
267             }
268             sub about {
269 0     0 0 0 my $self = shift;
270 0         0 $fx_cache->{registry}->[$self->registry_index]
271             }
272 0     0 0 0 sub track { $ti{$_[0]->chain} }
273 0     0 0 0 sub trackname { $_[0]->track->name }
274              
275             sub ladspa_id {
276 0     0 0 0 my $self = shift;
277 0         0 $Audio::Nama::fx_cache->{ladspa_label_to_unique_id}->{$self->type}
278             }
279             sub nameline {
280 0     0 0 0 my $self = shift;
281 0         0 my @attr_keys = qw( name surname fxname type ladspa_id bypassed trackname);
282 0         0 my $nameline = $self->id. ": ". join q(, ), grep{$_} map{$self->$_} @attr_keys;
  0         0  
  0         0  
283 0         0 $nameline .= "\n";
284 0         0 $nameline
285             }
286             sub _effect_index {
287 0     0   0 my $self = shift;
288 0         0 effect_index($self->type)
289             }
290             sub _modify_effect {
291 0     0   0 my ($self, $parameter, $value, $sign) = @_;
292 1     1   12 no warnings 'uninitialized';
  1         2  
  1         1624  
293 0         0 my $op_id = $self->id;
294              
295 0         0 $parameter--; # convert to zero-based
296 0         0 my $code = $self->type;
297 0         0 my $i = $self->_effect_index;
298 0 0       0 defined $i or confess "undefined effect code for $op_id: ",Audio::Nama::Dumper $self;
299 0         0 my $parameter_count = scalar @{ $self->about->{params} };
  0         0  
300 0 0 0     0 Audio::Nama::pager("$op_id: parameter (", $parameter + 1, ") out of range, skipping.\n"), return
301             unless ($parameter >= 0 and $parameter < $parameter_count);
302 0 0       0 Audio::Nama::pager("$op_id: parameter $parameter is read-only, skipping\n"), return
303             if $self->is_read_only($parameter);
304 0         0 my $new_value;
305 0 0       0 if ($sign) {
306 0         0 $new_value = eval
307             ( join " ",
308             $self->params->[$parameter],
309             $sign,
310             $value
311             );
312             }
313 0         0 else { $new_value = $value }
314 0         0 logpkg(__FILE__,__LINE__,'debug', "id $op_id p: $parameter, sign: $sign value: $value");
315 0         0 update_effect(
316             $op_id,
317             $parameter,
318             $new_value);
319 0         0 1
320             }
321             sub _remove_effect {
322 0     0   0 logsub("&_remove_effect");
323 0         0 my $self = shift;
324 0         0 my $id = $self->id;
325 0         0 my $n = $self->chain;
326 0         0 my $parent = $self->parent;
327 0         0 my $owns = $self->owns;
328 0 0       0 logpkg(__FILE__,__LINE__,'debug', "id: $id", ($parent ? ". parent: ".$parent->id : '' ));
329              
330 0 0       0 my $object = $parent ? q(controller) : q(chain operator);
331 0         0 logpkg(__FILE__,__LINE__,'debug', qq(ready to remove $object "$id" from track "$n"));
332              
333 0         0 $ui->remove_effect_gui($id);
334              
335             # recursively remove children
336            
337 0 0       0 logpkg(__FILE__,__LINE__,'debug',"children found: ". join ",",@$owns) if defined $owns;
338 0 0       0 map{ remove_effect($_) } @$owns if defined $owns;
  0         0  
339             ;
340             # remove chain operator
341            
342 0 0       0 if ( ! $parent ) { remove_op($id) }
  0         0  
343              
344             # remove controller
345            
346             else {
347            
348 0         0 remove_op($id);
349              
350             # remove parent ownership of deleted controller
351              
352 0         0 my $parent_owns = $parent->owns;
353 0         0 logpkg(__FILE__,__LINE__,'debug',"parent $parent owns: ". join ",", @$parent_owns);
354              
355 0         0 @$parent_owns = (grep {$_ ne $id} @$parent_owns);
  0         0  
356 0         0 logpkg(__FILE__,__LINE__,'debug',"parent $parent new owns list: ". join ",", @$parent_owns);
357              
358             }
359             # remove effect ID from track
360            
361 0 0       0 if( my $track = $ti{$n} ){
362 0         0 my @ops_list = @{$track->ops};
  0         0  
363             #say "ops_list: @ops_list";
364 0         0 my $perl_version = $^V;
365 0         0 my ($minor_version) = $perl_version =~ /^v5\.(\d+)/;
366 0         0 my @new_list = grep { $_ ne $id } @ops_list;
  0         0  
367             #say "new_list: @new_list";
368 0 0       0 if ($minor_version <= 14)
369 0         0 { $track->{ops} = [ @new_list ] }
370 0         0 else { @{ $track->{ops} } = @new_list }
  0         0  
371             }
372             #set_current_op($this_track->ops->[0]);
373             #set_current_param(1);
374 0         0 delete $by_id{$self->id};
375 0         0 return();
376             }
377             sub position_effect {
378 0     0 0 0 my($self, $pos) = @_;
379              
380 0         0 my $op = $self->id;
381            
382             # disabled, debugging needed
383             # we cannot handle controllers
384             #Audio::Nama::pager("$op or $pos: controller not allowed, skipping.\n"), return
385             # if grep{ fxn($_)->is_controller } $op, $pos;
386            
387             # first, modify track data structure
388            
389 0         0 my $track = $ti{$self->chain};
390              
391 0         0 my $op_index = $self->track_effect_index;
392 0         0 my @new_op_list = @{$track->ops};
  0         0  
393              
394             # remove op
395 0         0 splice @new_op_list, $op_index, 1;
396              
397 0 0       0 if ( $pos eq 'ZZZ'){
398             # put it at the end
399 0         0 push @new_op_list, $op;
400             }
401             else {
402 0         0 my $POS = fxn($pos);
403 0         0 my $track2 = $ti{$POS->chain};
404 0 0       0 Audio::Nama::pager("$pos: position belongs to a different track, skipping.\n"), return
405             unless $track eq $track2;
406 0         0 my $new_op_index = $POS->track_effect_index;
407             # insert op
408 0         0 splice @new_op_list, $new_op_index, 0, $op;
409             }
410             # reconfigure the entire engine (inefficient, but easy to do)
411 0         0 say join " - ",@new_op_list;
412 0         0 @{$track->ops} = @new_op_list;
  0         0  
413 0         0 Audio::Nama::request_setup();
414 0         0 $this_track = $track;
415             # this command generates spurious warnings during test
416 0         0 process_command('show_track');
417             }
418              
419             sub apply_op {
420 0     0 0 0 logsub("&apply_op");
421 0         0 my $self = shift;
422 0         0 local $config->{category} = 'ECI_FX';
423 0         0 my $id = $self->id;
424 0         0 logpkg(__FILE__,__LINE__,'debug', "id: $id");
425 0 0       0 logpkg(__FILE__,__LINE__,'logcluck', "$id: expected effect entry not found!"), return
426             if effect_entry_is_bad($id);
427 0         0 my $code = $self->type;
428 0         0 my $dad = $self->parent;
429 0         0 my $chain = $self->chain;
430 0         0 logpkg(__FILE__,__LINE__,'debug', "chain: $chain, type: $code");
431             # if code contains colon, then follow with comma (preset, LADSPA)
432             # if code contains no colon, then follow with colon (ecasound, ctrl)
433            
434 0 0       0 $code = '-' . $code . ($code =~ /:/ ? q(,) : q(:) );
435 0         0 my @vals = @{ $self->params };
  0         0  
436 0         0 logpkg(__FILE__,__LINE__,'debug', "values: @vals");
437              
438             # we start to build iam command
439              
440 0 0       0 my $add_cmd = $dad ? "ctrl-add " : "cop-add ";
441            
442 0         0 $add_cmd .= $code . join ",", @vals;
443              
444             # append the -kx operator for a controller-controller
445 0 0 0     0 $add_cmd .= " -kx" if $dad and $dad->is_controller;
446              
447 0         0 logpkg(__FILE__,__LINE__,'debug', "command: $add_cmd");
448              
449 0         0 Audio::Nama::eval_iam("c-select $chain");
450 0 0       0 Audio::Nama::eval_iam("cop-select " . $dad->ecasound_effect_index) if $dad;
451 0         0 Audio::Nama::eval_iam($add_cmd);
452 0 0       0 Audio::Nama::eval_iam("cop-bypass on") if $self->bypassed;
453              
454 0         0 my $owns = $self->owns;
455 0 0       0 (ref $owns) =~ /ARRAY/ or croak "expected array";
456 0         0 logpkg(__FILE__,__LINE__,'debug',"children found: ". join ",", @$owns);
457              
458             }
459              
460             #### Effect related routines, some exported, non-OO
461              
462             sub import_engine_subs {
463              
464 1     1 0 3 *valid_engine_setup = \&Audio::Nama::valid_engine_setup;
465 1         3 *engine_running = \&Audio::Nama::engine_running;
466 1         3 *eval_iam = \&Audio::Nama::eval_iam;
467 1         3 *ecasound_select_chain = \&Audio::Nama::ecasound_select_chain;
468 1         3 *sleeper = \&Audio::Nama::sleeper;
469 1         3 *process_command = \&Audio::Nama::process_command;
470 1         3 *pager = \&Audio::Nama::pager;
471 1         2 *this_op = \&Audio::Nama::this_op;
472 1         2 *this_param = \&Audio::Nama::this_param;
473 1         2 *this_stepsize = \&Audio::Nama::this_stepsize;
474             }
475              
476 1     1   6 use Exporter qw(import);
  1         2  
  1         181  
477             our %EXPORT_TAGS = ( 'all' => [ qw(
478              
479             effect_index
480             full_effect_code
481              
482             effect_entry_is_bad
483             check_fx_consistency
484              
485             new_effect_id
486             add_effect
487             _add_effect
488             append_effect
489             remove_effect
490             remove_fader_effect
491             modify_effect
492             modify_multiple_effects
493              
494             _update_effect
495             update_effect
496             sync_effect_parameters
497             find_op_offsets
498             apply_ops
499             expanded_ops_list
500            
501             bypass_effects
502            
503             restore_effects
504              
505             fxn
506              
507             set_current_op
508             set_current_param
509             set_current_stepsize
510             increment_param
511             decrement_param
512             set_parameter_value
513              
514             ) ] );
515              
516             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
517              
518             our @EXPORT = ();
519              
520 1     1   5 no warnings 'uninitialized'; # needed to avoid confusing test TAP output
  1         2  
  1         6213  
521             sub effect_entry_is_bad {
522 0     0 0   my $id = shift;
523             ! defined $id
524 0   0       or ! $Audio::Nama::Effect::by_id{$id}
525             }
526              
527             # make sure the chain number (track index) is set
528              
529             sub set_chain_value {
530            
531 0     0 0   my $p = shift;
532              
533 0 0         return if $p->{chain}; # return if already set
534            
535             # set chain from track if known
536            
537 0 0         if( $p->{track} )
    0          
    0          
538             {
539 0           $p->{chain} = $p->{track}->n;
540             delete $p->{track}
541 0           }
542              
543             # set chain from parent effect if known (add controller)
544            
545             elsif( $p->{parent_id})
546             {
547 0           $p->{chain} = fxn($p->{parent_id})->chain
548             }
549             # set chain from insert target if known (insert effect)
550            
551             elsif( $p->{before} )
552             {
553 0           $p->{chain} = fxn($p->{before})->chain;
554             }
555             #logpkg(__FILE__,__LINE__,'debug',(json_out($p));
556              
557             }
558              
559             # How effect chains are added (by default before fader)
560             # user command: add_effect
561             # add_effect(effect_chain => $fxc) calls insert_effect()
562             # insert_effect()
563             # * removes preceding operators
564             # * calls append_effect(effect_chain => $fxc)
565             # + which calls $fxc->add
566             # + which calls append_effect() for each effect
567             # * restores the operators
568            
569             sub add_effect {
570             #logsub('&add_effect');
571 0     0 0   my $args = shift;
572 0           my $added = _add_effect($args);
573 0           $added->[0]->id
574             }
575             sub _add_effect {
576 0     0     my $p = shift;
577 0           logsub("&_add_effect");
578             #logpkg(__FILE__,__LINE__,'debug',sub{ "add effect arguments - 0:\n".json_out($p)});
579            
580 0           set_chain_value($p);
581              
582             ### We prohibit creating effects on the Mixdown track
583              
584             ### We check $track->forbid_user_ops
585             ### which is set on the Mixdown track,
586              
587             ### An alternative would be giving each
588             ### Track its own add_effect method
589              
590             ### For now this is a single case
591              
592             die "user effects forbidden on this track"
593             if $ti{$p->{chain}}
594             and $ti{$p->{chain}}->forbid_user_ops
595 0 0 0       and $p->{type} !~ /$config->{latency_op}/;
      0        
596              
597 0     0     logpkg(__FILE__,__LINE__,'debug',sub{ "add effect arguments - 1:\n".json_out($p)});
  0            
598              
599             # either insert or add, depending on 'before' setting
600            
601 0 0 0       my $added = (defined $p->{before} and $p->{before} ne 'ZZZ')
602             ? insert_effect($p)
603             : append_effect($p);
604             }
605              
606             sub append_effect {
607 0     0 0   my $p = shift;
608 0           logsub("&append_effect",Dumper $p);
609 0           my %args = %$p;
610 0   0       $args{params} //= [];
611 0           my $track = $ti{$args{chain}};
612 0           my $add_effects_sub; # we will execute this with engine stopped
613             my @added;
614 0 0         if( $args{effect_chain})
615             {
616             # we will create and apply the effects later
617              
618 0     0     $add_effects_sub = sub{ $args{effect_chain}->add($track)};
  0            
619             }
620             else
621             {
622             # create the effect now, apply it later
623            
624             # assign defaults if no values supplied
625 0           my $count = $fx_cache->{registry}->[effect_index($args{type})]->{count} ;
626 0           my @defaults = @{fx_defaults($args{type})};
  0            
627 0 0         if( @defaults )
628             {
629 0           for my $i (0..$count - 1)
630             {
631             $args{params}[$i] = $defaults[$i]
632 0 0 0       if ! defined $args{params}[$i] or $args{params}[$i] eq '*'
633             }
634             }
635 0           my $FX = Audio::Nama::Effect->new(%args);
636 0           push @added, $FX;
637 0 0         if( ! $FX->name )
638             {
639 0           while( my($alias, $type) = each %{$fx->{alias}} )
  0            
640             {
641             $FX->set_name($track->unique_nickname($alias)),
642             # need to reset 'each'
643 0 0         keys %{$fx->{alias}}, last if $type eq $FX->type
  0            
644             }
645             }
646 0 0         $ui->add_effect_gui(\%args) unless $track->hide;
647              
648 0     0     $add_effects_sub = sub{ $FX->apply_op };
  0            
649             }
650 0 0         if( Audio::Nama::valid_engine_setup() )
651             {
652 0 0         if (Audio::Nama::engine_running())
653             {
654 0           $track->mute;
655 0           my $result = Audio::Nama::stop_do_start($add_effects_sub, 0.05);
656 0 0         push @added, @$result if is_array($result);
657 0           $track->unmute;
658             }
659             else {
660 0           my $result = $add_effects_sub->();
661 0 0         push @added, @$result if is_array($result);
662             }
663             }
664             \@added
665              
666 0           }
667 0     0 0   sub is_array { ref $_[0] eq 'ARRAY' }
668             sub insert_effect {
669 0     0 0   my $p = shift;
670 0           logsub("&insert_effect",Dumper $p);
671 0           my %args = %$p;
672 0           local $config->{category} = 'ECI_FX';
673 0 0         return(append_effect(\%args)) if $args{before} eq 'ZZZ';
674 0           my $running = Audio::Nama::engine_running();
675 0 0 0       pager("Cannot insert effect while engine is recording.\n"), return
676             if $running and Audio::Nama::ChainSetup::really_recording();
677             pager("Cannot insert effect before controller.\n"), return
678 0 0         if fxn($args{before})->is_controller;
679 0 0         if ($running){
680 0           $ui->stop_heartbeat;
681 0           Audio::Nama::mute();
682 0           Audio::Nama::stop_command();
683 0           sleeper( 0.05);
684             }
685 0 0         my $pos = fxn($args{before}) or die "$args{before}: effect ID not found";
686 0           my $track = $pos->track;
687 0 0         $this_track eq $pos->track or die "$args{before} is not on current track";
688             #
689             #logpkg(__FILE__,__LINE__,'debug', $track->name, $/;
690             #logpkg(__FILE__,__LINE__,'debug', "@{$track->ops}")
691              
692 0           my $offset = $pos->track_effect_index;
693 0           my $last_index = $#{$track->ops};
  0            
694              
695             # note ops after insertion point
696 0           my @after_ops = @{$track->ops}[$offset..$last_index];
  0            
697              
698             # remove corresponding chain operators from the engine
699 0           logpkg(__FILE__,__LINE__,'debug',"ops to remove and re-apply: @after_ops");
700 0           my $connected = Audio::Nama::eval_iam('cs-connected');
701 0 0         if ( $connected ){
702 0           map{ remove_op($_)} reverse @after_ops; # reverse order for correct index
  0            
703             }
704              
705             # remove the corresponding ids from the track list
706 0           splice @{$track->ops}, $offset;
  0            
707              
708             # add the new effect in the proper position
709 0           my $added = append_effect(\%args);
710              
711 0           logpkg(__FILE__,__LINE__,'debug',"@{$track->ops}");
  0            
712              
713             # replace the effects that had been removed
714 0           push @{$track->ops}, @after_ops;
  0            
715              
716 0     0     logpkg(__FILE__,__LINE__,'debug',sub{"@{$track->ops}"});
  0            
  0            
717              
718             # replace the corresponding Ecasound chain operators
719 0 0         if ($connected ){
720 0           map{ fxn($_)->apply_op } @after_ops;
  0            
721             }
722            
723 0 0         if ($running){
724 0           Audio::Nama::eval_iam('start');
725 0           sleeper(0.3);
726 0           Audio::Nama::unmute();
727 0           $ui->start_heartbeat;
728             }
729 0           $added;
730             }
731             sub modify_effect {
732 0     0 0   logsub("&modify_effect");
733 0           my ($op_id, $parameter, $sign, $value) = @_;
734             # $parameter: one-based
735            
736 0 0         my $FX = fxn($op_id)
737             or pager("$op_id: non-existing effect id. Skipping.\n"), return;
738 0           $FX->_modify_effect($parameter, $value, $sign);
739             }
740              
741              
742             sub modify_multiple_effects {
743 0     0 0   logsub("&modify_multiple_effects");
744 0           my ($op_ids, $parameters, $sign, $value) = @_;
745 0           map{ my $op_id = $_;
  0            
746 0           map{ my $parameter = $_;
  0            
747 0           modify_effect($op_id, $parameter, $sign, $value);
748 0           set_current_op($op_id);
749 0           set_current_param($parameter);
750             } @$parameters;
751             } @$op_ids;
752             }
753              
754             sub remove_effect {
755 0     0 0   logsub("&remove_effect");
756 0           my $id = shift;
757 0 0         my $FX = fxn($id)
758             or logpkg(__FILE__,__LINE__,'logcarp',"$id: does not exist, skipping...\n"), return;
759 0           $FX->_remove_effect;
760             }
761              
762             sub full_effect_code {
763             # get text effect code from user input, which could be
764             # - LADSPA Unique ID (number)
765             # - LADSPA Label (el:something)
766             # - abbreviated LADSPA label (something)
767             # - Ecasound operator (something)
768             # - abbreviated Ecasound preset (something)
769             # - Ecasound preset (pn:something)
770             # - user alias
771            
772             # there is no interference in these labels at present,
773             # so we offer the convenience of using them without
774             # el: and pn: prefixes.
775            
776 0     0 0   my $input = shift;
777 0           my $code;
778 0 0         if ($input !~ /\D/) # i.e. $input is all digits
    0          
779             {
780 0           $code = $fx_cache->{ladspa_id_to_label}->{$input};
781             }
782             elsif ( $fx_cache->{full_label_to_index}->{$input} )
783             {
784 0           $code = $input
785             }
786             else
787             {
788 0           $code = $fx_cache->{partial_label_to_full}->{$input}
789             }
790 0           $code
791             }
792              
793              
794             # get integer effect index for Nama effect registry
795             # e.g. ea => 2
796             sub effect_index {
797 0     0 0   my $code = shift;
798 0           my $i = $fx_cache->{full_label_to_index}->{full_effect_code($code)};
799 0 0 0       defined $i or $config->{opts}->{E} or warn("$code: effect index not found\n");
800 0           $i
801             }
802              
803             sub fx_defaults {
804 0     0 0   my $code = shift;
805 0           my $i = effect_index($code);
806 0           my $values = [];
807 0           foreach my $p ( @{ $fx_cache->{registry}->[$i]->{params} })
  0            
808             {
809 0 0         return [] unless defined $p->{default};
810 0           push @$values, $p->{default};
811             }
812             $values
813 0           }
814            
815              
816             ## Ecasound engine -- apply/remove chain operators
817              
818             sub apply_ops { # in addition to operators in .ecs file
819 0     0 0   logsub("&apply_ops");
820 0           for my $track ( Audio::Nama::audio_tracks() ) {
821 0           my $n = $track->n;
822 0 0         next unless Audio::Nama::ChainSetup::is_ecasound_chain($n);
823 0           logpkg(__FILE__,__LINE__,'debug', "chain: $n, offset: $fx->{offset}->{$n}");
824 0           $track->apply_ops;
825             }
826 0 0         ecasound_select_chain($this_track->n) if defined $this_track;
827             }
828              
829             sub remove_op {
830             # remove chain operator from Ecasound engine
831              
832 0     0 0   logsub("&remove_op");
833 0           local $config->{category} = 'ECI_FX';
834              
835             # only if engine is configured
836 0 0         return unless Audio::Nama::valid_engine_setup();
837              
838 0           my $id = shift;
839 0           my $self = fxn($id);
840 0           my $n = $self->chain;
841              
842             # select chain
843            
844 0 0         return unless ecasound_select_chain($n);
845              
846             # deal separately with controllers and chain operators
847            
848 0           my $index;
849              
850 0 0         if ( ! $self->is_controller) { # chain operator
851 0           logpkg(__FILE__,__LINE__,'debug', "no parent, assuming chain operator");
852            
853 0           $index = $self->ecasound_effect_index;
854 0           logpkg(__FILE__,__LINE__,'debug', "ops list for chain $n: @{$ti{$n}->ops}");
  0            
855 0           logpkg(__FILE__,__LINE__,'debug', "operator id to remove: $id");
856 0           logpkg(__FILE__,__LINE__,'debug', "ready to remove from chain $n, operator id $id, index $index");
857 0     0     logpkg(__FILE__,__LINE__,'debug',sub{Audio::Nama::eval_iam("cs")});
  0            
858 0           Audio::Nama::eval_iam("cop-select ". $self->ecasound_effect_index);
859 0     0     logpkg(__FILE__,__LINE__,'debug',sub{"selected operator: ". Audio::Nama::eval_iam("cop-selected")});
  0            
860 0           Audio::Nama::eval_iam("cop-remove");
861 0     0     logpkg(__FILE__,__LINE__,'debug',sub{Audio::Nama::eval_iam("cs")});
  0            
862              
863             } else { # controller
864              
865 0           logpkg(__FILE__,__LINE__,'debug', "has parent, assuming controller");
866              
867 0           my $ctrl_index = $self->ecasound_controller_index;
868 0           logpkg(__FILE__,__LINE__,'debug', Audio::Nama::eval_iam("cs"));
869 0           Audio::Nama::eval_iam("cop-select ". $self->root_parent->ecasound_controller_index);
870 0           logpkg(__FILE__,__LINE__,'debug', "selected operator: ". Audio::Nama::eval_iam("cop-selected"));
871 0           Audio::Nama::eval_iam("ctrl-select $ctrl_index");
872 0           Audio::Nama::eval_iam("ctrl-remove");
873 0           logpkg(__FILE__,__LINE__,'debug', Audio::Nama::eval_iam("cs"));
874             }
875             }
876              
877              
878             # Track sax effects: A B C GG HH II D E F
879             # GG HH and II are controllers applied to chain operator C
880             #
881             # to remove controller HH:
882             #
883             # for Ecasound, chain op index = 3,
884             # ctrl index = 2
885             # = track_effect_index HH - track_effect_index C
886             #
887             #
888             # for Nama, chain op array index 2,
889             # ctrl arrray index = chain op array index + ctrl_index
890             # = effect index - 1 + ctrl_index
891             #
892             #
893              
894             ## Nama effects
895              
896             ## have a unique ID from capital letters
897             ## IDs are kept in the $track->ops
898              
899             ## Rules for allocating IDs
900             ## new_effect_id() - issues a new ID
901             ## effect_init() - initializes a Nama effect, should be called effect_init()
902             ## add_effect
903              
904             sub new_effect_id {
905              
906             # increment $fx->{id_counter} if necessary
907             # to find an unused effect_id
908            
909 0     0 0   while( fxn($fx->{id_counter})){ $fx->{id_counter}++};
  0            
910             $fx->{id_counter}
911 0           }
912              
913              
914              
915             ## synchronize Ecasound chain operator parameters
916             # with Nama effect parameter
917              
918             sub _update_effect {
919 0     0     local $config->{category} = 'ECI_FX';
920              
921             # update the parameters of the Ecasound chain operator
922             # referred to by a Nama operator_id
923            
924             #logsub("&update_effect");
925              
926 0 0         return unless Audio::Nama::valid_engine_setup();
927             #my $es = Audio::Nama::eval_iam("engine-status");
928             #logpkg(__FILE__,__LINE__,'debug', "engine is $es");
929             #return if $es !~ /not started|stopped|running/;
930              
931 0           my ($id, $param, $val) = @_;
932              
933 0 0         my $FX = fxn($id) or carp("$id: effect not found. skipping...\n"), return;
934 0           $param++; # so the value at $p[0] is applied to parameter 1
935 0           my $chain = $FX->chain;
936 0 0         return unless Audio::Nama::ChainSetup::is_ecasound_chain($chain);
937              
938 0           logpkg(__FILE__,__LINE__,'debug', "chain $chain id $id param $param value $val");
939              
940             # $param is zero-based.
941             # $FX->params is zero-based.
942              
943 0 0         my $old_chain = Audio::Nama::eval_iam('c-selected') if Audio::Nama::valid_engine_setup();
944 0           ecasound_select_chain($chain);
945              
946             # update Ecasound's copy of the parameter
947 0 0         if( $FX->is_controller ){
948 0           my $i = $FX->ecasound_controller_index;
949 0           logpkg(__FILE__,__LINE__,'debug', "controller $id: track: $chain, index: $i param: $param, value: $val");
950 0           Audio::Nama::eval_iam("ctrl-select $i");
951 0           Audio::Nama::eval_iam("ctrlp-select $param");
952 0           Audio::Nama::eval_iam("ctrlp-set $val");
953             }
954             else { # is operator
955 0           my $i = $FX->ecasound_operator_index;
956 0           logpkg(__FILE__,__LINE__,'debug', "operator $id: track $chain, index: $i, offset: ". $FX->offset . " param $param, value $val");
957 0           Audio::Nama::eval_iam("cop-select ". ($FX->offset + $i));
958 0           Audio::Nama::eval_iam("copp-select $param");
959 0           Audio::Nama::eval_iam("copp-set $val");
960             }
961 0           ecasound_select_chain($old_chain);
962             }
963              
964             # set both Nama effect and Ecasound chain operator
965             # parameters
966              
967             sub update_effect {
968 0     0 0   my ($id, $param, $val) = @_;
969 0           _update_effect( @_ );
970 0 0         return if ! defined fxn($id);
971 0           fxn($id)->params->[$param] = $val;
972             }
973              
974             sub sync_effect_parameters {
975 0     0 0   local $config->{category} = 'ECI_FX';
976              
977             # when a controller changes an effect parameter, the
978             # parameter value can differ from Nama's value for that
979             # parameter.
980             #
981             # this routine syncs them in prep for save_state()
982            
983 0 0         return unless Audio::Nama::valid_engine_setup();
984 0           my $old_chain = Audio::Nama::eval_iam('c-selected');
985 0           map{ $_->sync_one_effect } grep{ $_ } map{ fxn($_) } ops_with_controller(), ops_with_read_only_params();
  0            
  0            
  0            
986 0           Audio::Nama::eval_iam("c-select $old_chain");
987             }
988              
989            
990              
991             sub get_ecasound_cop_params {
992 0     0 0   local $config->{category} = 'ECI_FX';
993 0           my $count = shift;
994 0           my @params;
995 0           for (1..$count){
996 0           Audio::Nama::eval_iam("copp-select $_");
997 0           push @params, Audio::Nama::eval_iam("copp-get");
998             }
999             \@params
1000 0           }
1001            
1002             sub ops_with_controller {
1003 0           grep{ ! $_->is_controller }
1004 0           grep{ scalar @{$_->owns} }
  0            
1005 0           map{ fxn($_) }
1006 0     0 0   map{ @{ $_->ops } }
  0            
  0            
1007             Audio::Nama::ChainSetup::engine_tracks();
1008             }
1009             sub ops_with_read_only_params {
1010 0           grep{ $_->has_read_only_param() }
1011 0           map{ fxn($_) }
1012 0     0 0   map{ @{ $_->ops } }
  0            
  0            
1013             Audio::Nama::ChainSetup::engine_tracks();
1014             }
1015              
1016              
1017             sub find_op_offsets {
1018              
1019 0     0 0   local $config->{category} = 'ECI_FX';
1020 0           logsub("&find_op_offsets");
1021 0           my @op_offsets = grep{ /"\d+"/} split "\n",Audio::Nama::eval_iam("cs");
  0            
1022 0           logpkg(__FILE__,__LINE__,'debug', join "\n\n",@op_offsets);
1023 0           for my $output (@op_offsets){
1024 0           my $chain_id;
1025 0           ($chain_id) = $output =~ m/Chain "(\w*\d+)"/;
1026             # "print chain_id: $chain_id\n";
1027 0 0         next if $chain_id =~ m/\D/; # skip id's containing non-digits
1028             # i.e. M1
1029 0           my $quotes = $output =~ tr/"//;
1030 0           logpkg(__FILE__,__LINE__,'debug', "offset: $quotes in $output");
1031 0           $fx->{offset}->{$chain_id} = $quotes/2 - 1;
1032             }
1033             }
1034              
1035             sub expanded_ops_list { # including controllers
1036             # we assume existing ops
1037 0     0 0   my @ops_list = @_;
1038 0 0         return () unless @_;
1039 0           my @expanded = ();
1040             map
1041 0           { push @expanded,
1042             $_,
1043 0           expanded_ops_list( reverse @{fxn($_)->owns} );
  0            
1044              
1045             # we reverse controllers listing so
1046             # the first controller is applied last
1047             # the insert operation places it adjacent to
1048             # its parent controller
1049             # as a result, the controllers end up
1050             # in the same order as the original
1051             #
1052             # which is convenient for RCS
1053            
1054             } @ops_list;
1055              
1056 0           my %seen;
1057 0           @expanded = grep { ! $seen{$_}++ } @expanded;
  0            
1058             }
1059              
1060             sub intersect_with_track_ops_list {
1061 0     0 0   my ($track, @effects) = @_;
1062 0           my %ops;
1063 0           map{ $ops{$_}++} @{$track->ops};
  0            
  0            
1064 0           my @intersection = grep { $ops{$_} } @effects;
  0            
1065 0           my @outersection = grep { !$ops{$_} } @effects;
  0            
1066 0 0         carp "@outersection: effects don't belong to track: ", $track->name,
1067             ". skipping." if @outersection;
1068             @intersection
1069 0           }
1070              
1071             sub bypass_effects {
1072 0     0 0   my($track, @ops) = @_;
1073 0           set_bypass_state($track, 'on', @ops);
1074             }
1075             sub restore_effects {
1076 0     0 0   my($track, @ops) = @_;
1077 0           set_bypass_state($track, 'off', @ops);
1078             }
1079              
1080             sub set_bypass_state {
1081            
1082 0     0 0   local $config->{category} = 'ECI_FX';
1083 0           my($track, $bypass_state, @ops) = @_;
1084              
1085             # only process ops that belong to this track
1086 0           @ops = intersect_with_track_ops_list($track,@ops);
1087              
1088 0           $track->mute;
1089 0           Audio::Nama::eval_iam("c-select ".$track->n);
1090              
1091 0           foreach my $op ( @ops)
1092             {
1093 0           my $FX = fxn($op);
1094 0           my $i = $FX->ecasound_effect_index;
1095 0           Audio::Nama::eval_iam("cop-select $i");
1096 0           Audio::Nama::eval_iam("cop-bypass $bypass_state");
1097 0 0         $FX->set(bypassed => ($bypass_state eq 'on') ? 1 : 0);
1098             }
1099 0           $track->unmute;
1100             }
1101              
1102             sub remove_fader_effect {
1103 0     0 0   my ($track, $role) = @_;
1104 0           remove_effect($track->$role);
1105 0           delete $track->{$role}
1106             }
1107             # Object interface for effects
1108              
1109             sub fxn {
1110 0     0 0   my $id = shift;
1111 0           $by_id{$id};
1112             }
1113             sub set_current_op {
1114 0     0 0   my $op_id = shift;
1115 0           my $FX = fxn($op_id);
1116 0 0         return unless $FX;
1117 0           my $track = $ti{$FX->chain};
1118 0           $project->{current_op}->{$track->name} = $op_id;
1119             }
1120             sub set_current_param {
1121 0     0 0   my $parameter = shift;
1122 0           $project->{current_param}->{Audio::Nama::this_op()} = $parameter;
1123             }
1124             sub set_current_stepsize {
1125 0     0 0   my $stepsize = shift;
1126 0           $project->{current_stepsize}->{Audio::Nama::this_op()}->[this_param()] = $stepsize;
1127             }
1128 0     0 0   sub increment_param { modify_effect(Audio::Nama::this_op(), this_param(),'+',this_stepsize())}
1129 0     0 0   sub decrement_param { modify_effect(Audio::Nama::this_op(), this_param(),'-',this_stepsize())}
1130             sub set_parameter_value {
1131 0     0 0   my $value = shift;
1132 0           modify_effect(Audio::Nama::this_op(), this_param(), undef, $value)
1133             }
1134              
1135              
1136             sub check_fx_consistency {
1137              
1138 0     0 0   my $result = {};
1139 0           my %seen_ids;
1140             my $is_error;
1141             map
1142             {
1143 0           my $track = $_;
  0            
1144 0           my $name = $track->name;
1145 0           my @ops = @{ $track->{ops} };
  0            
1146 0           my $is_track_error;
1147              
1148             # check for missing special-purpose ops
1149              
1150 0           my $no_vol_op = ! $track->vol;
1151 0           my $no_pan_op = ! $track->pan;
1152 0           my $no_latency_op = ! $track->latency_op;
1153              
1154             # check for orphan special-purpose op entries
1155              
1156             $is_track_error++, $result->{track}->{$name}->{orphan_vol} = $track->vol
1157 0 0 0       if $track->vol and ! grep { $track->vol eq $_ } @ops;
  0            
1158             $is_track_error++,$result->{track}->{$name}->{orphan_pan} = $track->pan
1159 0 0 0       if $track->pan and ! grep { $track->pan eq $_ } @ops;
  0            
1160              
1161             # we don't check for orphan latency ops as this is
1162             # allowed in order to keep constant $op_id over
1163             # time (slower incrementing of fx counter)
1164            
1165             #$is_track_error++,$result->{track}->{$name}->{orphan_latency_op} = $track->latency_op
1166             # if $track->latency_op and ! grep { $track->latency_op eq $_ } @ops;
1167              
1168             # check for undefined op ids
1169            
1170 0           my @track_undef_op_pos;
1171              
1172 0           my $i = 0;
1173 0 0         map { defined $_ or push @track_undef_op_pos, $i; $i++ } @ops;
  0            
  0            
1174             $is_track_error++,$result->{track}->{$name}->{undef_op_pos}
1175 0 0         = \@track_undef_op_pos if @track_undef_op_pos;
1176              
1177             # remove undefined op ids from list
1178            
1179 0           @ops = grep{ $_ } @ops;
  0            
1180              
1181             # check for op ids without corresponding entry
1182              
1183 0           my @uninstantiated_op_ids;
1184 0 0         map { fxn($_) or push @uninstantiated_op_ids, $_ } @ops;
  0            
1185              
1186             $is_track_error++, $result->{track}->{$name}->{uninstantiated_op_ids}
1187 0 0         = \@uninstantiated_op_ids if @uninstantiated_op_ids;
1188              
1189 0 0         $result->{track}->{$name}->{is_error}++ if $is_track_error;
1190 0 0         $result->{is_error}++ if $is_track_error;
1191             } Audio::Nama::audio_tracks();
1192              
1193             # check for objects missing fields
1194            
1195             my @incomplete_entries =
1196 0   0       grep { ! fxn($_)->params or ! fxn($_)->type or ! fxn($_)->chain }
1197 0           grep { $_ } keys %Audio::Nama::Effect::by_id;
  0            
1198              
1199 0 0         if(@incomplete_entries)
1200             {
1201 0           $result->{incomplete_entries} = \@incomplete_entries;
1202 0           $result->{is_error}++
1203             }
1204 0           $result;
1205             }
1206              
1207             sub fade {
1208 0     0 0   my $self = shift;
1209             # parameter starts at one
1210 0           my ($param, $from, $to, $seconds) = @_;
1211              
1212 0           my $id = $self->id;
1213             # no fade without Timer::HiRes
1214             # no fade unless engine is running
1215 0 0 0       if ( engine_running() and $config->{hires_timer} )
1216             {
1217 0           my $steps = $seconds * $config->{fade_resolution};
1218 0           my $wink = 1/$config->{fade_resolution};
1219 0           my $size = ($to - $from)/$steps;
1220 0           logpkg(__FILE__,__LINE__,'debug', "id: $id, param: $param, from: $from, to: $to, seconds: $seconds");
1221             # first step by step
1222 0           for (1..$steps - 1){
1223 0           $self->_modify_effect($param, $size, '+');
1224 0           sleeper( $wink );
1225             }
1226             }
1227 0           $self->_modify_effect($param, $to)
1228             }
1229              
1230             sub fadein {
1231 0     0 0   my $self = shift;
1232 0           my $to = shift;
1233 0           my $from = $config->{fade_out_level}->{$self->type};
1234 0           $self->_modify_effect(1, $from);
1235 0           $self->fade(1, $from, $to, $config->{engine_fade_length_on_start_stop});
1236             }
1237             sub fadeout {
1238 0     0 0   my $self = shift;
1239 0           my $from = $self->params->[0];
1240 0           my $to = $config->{fade_out_level}->{$self->type};
1241 0           $self->fade(1, $from, $to, $config->{engine_fade_length_on_start_stop} );
1242 0           $self->_modify_effect(1, $config->{mute_level}->{$self->type});
1243             }
1244             sub mute_level {
1245 0     0 0   my $self = shift;
1246 0           my $level = $config->{mute_level}->{$self->type};
1247             #defined $level or die $self->nameline . " cannot be muted."
1248 0           $level
1249             }
1250             sub fade_out_level {
1251 0     0 0   my $self = shift;
1252 0           $config->{fade_out_level}->{$self->type}
1253             }
1254              
1255             } # end package Effect
1256              
1257             1