| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ------------- Effect-Chain and -Profile routines -------- | 
| 2 |  |  |  |  |  |  | # Effect Chains | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # we have two type of effect chains | 
| 5 |  |  |  |  |  |  | # + global effect chains - usually user defined, available to all projects | 
| 6 |  |  |  |  |  |  | # + system generated effect chains, per project | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | { | 
| 9 |  |  |  |  |  |  | package Audio::Nama::EffectChain; | 
| 10 | 1 |  |  | 1 |  | 6 | use Modern::Perl; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 7 |  | 
| 11 | 1 |  |  | 1 |  | 112 | use Data::Dumper::Concise; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 61 |  | 
| 12 | 1 |  |  | 1 |  | 5 | use Carp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 13 | 1 |  |  | 1 |  | 5 | use Exporter qw(import); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 88 |  | 
| 14 | 1 |  |  | 1 |  | 12 | use Storable qw(dclone); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 57 |  | 
| 15 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Effect  qw(fxn append_effect); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 16 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Log qw(logpkg logsub); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 17 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Assign qw(json_out); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 49 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw($fx_cache %tn $fx); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 172 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | our $AUTOLOAD; | 
| 22 |  |  |  |  |  |  | our $VERSION = 0.001; | 
| 23 | 1 |  |  | 1 |  | 6 | no warnings qw(uninitialized); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 87 |  | 
| 24 |  |  |  |  |  |  | our @ISA; | 
| 25 |  |  |  |  |  |  | our ($n, %by_index, @attributes, %is_attribute); | 
| 26 | 1 |  |  |  |  | 7 | use Audio::Nama::Object qw( | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | n | 
| 29 |  |  |  |  |  |  | ops_list | 
| 30 |  |  |  |  |  |  | ops_data | 
| 31 |  |  |  |  |  |  | inserts_data | 
| 32 |  |  |  |  |  |  | region | 
| 33 |  |  |  |  |  |  | attrib | 
| 34 |  |  |  |  |  |  | class | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 1 |  |  | 1 |  | 5 | ); | 
|  | 1 |  |  |  |  | 3 |  | 
| 37 |  |  |  |  |  |  | @attributes = qw( | 
| 38 |  |  |  |  |  |  | name | 
| 39 |  |  |  |  |  |  | bypass | 
| 40 |  |  |  |  |  |  | id | 
| 41 |  |  |  |  |  |  | project | 
| 42 |  |  |  |  |  |  | global | 
| 43 |  |  |  |  |  |  | profile | 
| 44 |  |  |  |  |  |  | user | 
| 45 |  |  |  |  |  |  | system | 
| 46 |  |  |  |  |  |  | track_name | 
| 47 |  |  |  |  |  |  | track_version_result | 
| 48 |  |  |  |  |  |  | track_version_original | 
| 49 |  |  |  |  |  |  | track_target_original | 
| 50 |  |  |  |  |  |  | insert | 
| 51 |  |  |  |  |  |  | track_cache | 
| 52 |  |  |  |  |  |  | track_target | 
| 53 |  |  |  |  |  |  | ) ; | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | ### 	attributes for searching, sorting, used by external functions | 
| 56 |  |  |  |  |  |  | # 		name				# for user-defined effect chains | 
| 57 |  |  |  |  |  |  | # | 
| 58 |  |  |  |  |  |  | # 		bypass				# used for identifying effect bypass (obsolete) | 
| 59 |  |  |  |  |  |  | # 		id					# effect id, for storing single effect with controllers | 
| 60 |  |  |  |  |  |  | # 							# for bypass (probably obsolete) | 
| 61 |  |  |  |  |  |  | # | 
| 62 |  |  |  |  |  |  | # 		project				# true value identifies project-specific effect chain | 
| 63 |  |  |  |  |  |  | # | 
| 64 |  |  |  |  |  |  | # 		global				# true value identified global effect chain, | 
| 65 |  |  |  |  |  |  | # 							# not project specific, usually user-defined | 
| 66 |  |  |  |  |  |  | # | 
| 67 |  |  |  |  |  |  | # 		profile				# name of associated effect profile | 
| 68 |  |  |  |  |  |  | # | 
| 69 |  |  |  |  |  |  | # 		user				# true value identifies user created effect chain | 
| 70 |  |  |  |  |  |  | # | 
| 71 |  |  |  |  |  |  | # 		system				# true value identifies system generated effect chain | 
| 72 |  |  |  |  |  |  | # | 
| 73 |  |  |  |  |  |  | # 		track_name			# applies to a track of this name | 
| 74 |  |  |  |  |  |  | # | 
| 75 |  |  |  |  |  |  | # 		track_version_result 	# WAV version of track after caching | 
| 76 |  |  |  |  |  |  | # | 
| 77 |  |  |  |  |  |  | # 		track_version_original 	# WAV version of track before caching | 
| 78 |  |  |  |  |  |  | # | 
| 79 |  |  |  |  |  |  | # 		insert				# true value identifies belonging to an insert | 
| 80 |  |  |  |  |  |  | # | 
| 81 |  |  |  |  |  |  | # 		track_cache			# true value identifies belonging to track caching | 
| 82 |  |  |  |  |  |  | # | 
| 83 |  |  |  |  |  |  | # 		track_target_original #	WAV files were from this track | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | %is_attribute = map{ $_ => 1 } @attributes; | 
| 86 |  |  |  |  |  |  | initialize(); | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | ## sugar for accessing individual effect attributes | 
| 89 |  |  |  |  |  |  | ## similar sugar is used for effects. | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | sub is_controller { | 
| 92 | 0 |  |  | 0 | 0 | 0 | my ($self, $id) = @_; | 
| 93 |  |  |  |  |  |  | $self->{ops_data}->{$id}->{belongs_to} | 
| 94 | 0 |  |  |  |  | 0 | } | 
| 95 |  |  |  |  |  |  | sub parent_id : lvalue { | 
| 96 | 0 |  |  | 0 | 0 | 0 | my ($self, $id) = @_; | 
| 97 |  |  |  |  |  |  | $self->{ops_data}->{$id}->{belongs_to} | 
| 98 | 0 |  |  |  |  | 0 | } | 
| 99 |  |  |  |  |  |  | sub type { | 
| 100 | 0 |  |  | 0 | 0 | 0 | my ($self, $id) = @_; | 
| 101 |  |  |  |  |  |  | $self->{ops_data}->{$id}->{type} | 
| 102 | 0 |  |  |  |  | 0 | } | 
| 103 |  |  |  |  |  |  | sub params { | 
| 104 | 0 |  |  | 0 | 0 | 0 | my ($self, $id) = @_; | 
| 105 |  |  |  |  |  |  | $self->{ops_data}->{$id}->{params} | 
| 106 | 0 |  |  |  |  | 0 | } | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | sub initialize { | 
| 109 | 1 |  |  | 1 | 0 | 3 | $n = 0; | 
| 110 | 1 |  |  |  |  | 3 | %by_index = (); | 
| 111 |  |  |  |  |  |  | } | 
| 112 | 0 | 0 |  | 0 | 0 |  | sub new_index { $n++; $by_index{$n} ?  new_index() : $n } | 
|  | 0 |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | sub new { | 
| 114 |  |  |  |  |  |  | # arguments: ops_list, ops_data, inserts_data | 
| 115 |  |  |  |  |  |  | # ops_list => [id1, id2, id3,...]; | 
| 116 | 0 |  |  | 0 | 0 |  | my $class = shift; | 
| 117 | 0 |  |  |  |  |  | my %vals = @_; | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | # we need to so some preparation if we are creating | 
| 120 |  |  |  |  |  |  | # an effect chain for the first time (as opposed | 
| 121 |  |  |  |  |  |  | # to restoring a serialized effect chain) | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 | 0 |  |  |  |  | if (! $vals{n} ) { | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | # move secondary attributes to $self->{attrib}->{...} | 
| 126 | 0 |  |  |  |  |  | move_attributes(\%vals); | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 0 |  |  |  |  |  | $vals{n} = new_index(); | 
| 129 | 0 |  | 0 |  |  |  | $vals{inserts_data} ||= []; | 
| 130 | 0 |  | 0 |  |  |  | $vals{ops_list} 	||= []; | 
| 131 | 0 |  | 0 |  |  |  | $vals{ops_data} 	||= {}; | 
| 132 | 0 | 0 |  |  |  |  | croak "undeclared field in: @_" if grep{ ! $_is_field{$_} } keys %vals; | 
|  | 0 |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | croak "must have exactly one of 'global' or 'project' fields defined" | 
| 134 | 0 | 0 | 0 |  |  |  | unless ($vals{attrib}{global} xor $vals{attrib}{project}); | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  | 0 |  |  | logpkg(__FILE__,__LINE__,'debug','constructor arguments ', sub{ json_out(\%vals) }); | 
|  | 0 |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | # we expect some effects | 
| 139 |  |  |  |  |  |  | logpkg(__FILE__,__LINE__,'warn',"Nether ops_list or nor insert_data is present") | 
| 140 | 0 | 0 | 0 |  |  |  | if ! scalar @{$vals{ops_list}} and ! scalar @{$vals{inserts_data}}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 0 |  |  |  |  |  | my $ops_data = {}; | 
| 143 |  |  |  |  |  |  | # ops data is taken preferentially | 
| 144 |  |  |  |  |  |  | # from ops_data argument, with fallback | 
| 145 |  |  |  |  |  |  | # to existing effects | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | # in both cases, we clone the data structures | 
| 148 |  |  |  |  |  |  | # to ensure we don't damage the original | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | map { | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 | 0 |  |  |  |  | if ( $vals{ops_data}->{$_} ) | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | { | 
| 155 | 0 |  |  |  |  |  | $ops_data->{$_} 		  = dclone($vals{ops_data}->{$_}); | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  | else | 
| 158 |  |  |  |  |  |  | { | 
| 159 | 0 |  |  |  |  |  | my $filtered_op_data = dclone( fxn($_)->as_hash );# copy | 
| 160 | 0 |  |  |  |  |  | my @unwanted_keys = qw( chain bypassed name surname display); | 
| 161 | 0 |  |  |  |  |  | delete $filtered_op_data->{$_} for @unwanted_keys; | 
| 162 | 0 |  |  |  |  |  | $ops_data->{$_} = $filtered_op_data; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 0 |  |  |  |  |  | } @{$vals{ops_list}}; | 
|  | 0 |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 0 |  |  |  |  |  | $vals{ops_data} = $ops_data; | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 0 | 0 |  |  |  |  | if( scalar @{$vals{inserts_data}}) | 
|  | 0 |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | { | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | # rewrite inserts to store what we need: | 
| 174 |  |  |  |  |  |  | # 1. for general-purpose effects chain use | 
| 175 |  |  |  |  |  |  | # 2. for track caching use | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | $vals{inserts_data} = | 
| 179 |  |  |  |  |  |  | [ | 
| 180 |  |  |  |  |  |  | map | 
| 181 |  |  |  |  |  |  | { | 
| 182 | 0 |  |  | 0 |  |  | logpkg(__FILE__,__LINE__,'debug',"insert: ", sub{Dumper $_}); | 
|  | 0 |  |  |  |  |  |  | 
| 183 | 0 |  |  |  |  |  | my @wet_ops = @{$tn{$_->wet_name}->ops}; | 
|  | 0 |  |  |  |  |  |  | 
| 184 | 0 |  |  |  |  |  | my @dry_ops = @{$tn{$_->dry_name}->ops}; | 
|  | 0 |  |  |  |  |  |  | 
| 185 | 0 |  |  |  |  |  | my $wet_effect_chain = Audio::Nama::EffectChain->new( | 
| 186 |  |  |  |  |  |  | project => 1, | 
| 187 |  |  |  |  |  |  | insert	=> 1, | 
| 188 |  |  |  |  |  |  | ops_list => \@wet_ops, | 
| 189 |  |  |  |  |  |  | ); | 
| 190 | 0 |  |  |  |  |  | my $dry_effect_chain = Audio::Nama::EffectChain->new( | 
| 191 |  |  |  |  |  |  | project => 1, | 
| 192 |  |  |  |  |  |  | insert => 1, | 
| 193 |  |  |  |  |  |  | ops_list => \@dry_ops, | 
| 194 |  |  |  |  |  |  | ); | 
| 195 | 0 |  |  |  |  |  | my $hash = dclone($_->as_hash); | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 0 |  |  |  |  |  | $hash->{wet_effect_chain} = $wet_effect_chain->n; | 
| 198 | 0 |  |  |  |  |  | $hash->{dry_effect_chain} = $dry_effect_chain->n; | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 0 |  |  |  |  |  | map{ delete $hash->{$_} } qw(n dry_vol wet_vol track); | 
|  | 0 |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # Reasons for deleting insert attributes | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | # n: we'll get a new index when we re-apply | 
| 205 |  |  |  |  |  |  | # dry_vol, wet_vol: will never be re-allocated | 
| 206 |  |  |  |  |  |  | #    so why not reuse them? | 
| 207 |  |  |  |  |  |  | #    except for general purpose we'd like to | 
| 208 |  |  |  |  |  |  | #    re-allocate | 
| 209 |  |  |  |  |  |  | # track: we already know the track from | 
| 210 |  |  |  |  |  |  | #    the parent effect chain | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | # What is left: | 
| 213 |  |  |  |  |  |  | # | 
| 214 |  |  |  |  |  |  | # 	class | 
| 215 |  |  |  |  |  |  | #	wetness | 
| 216 |  |  |  |  |  |  | #	send_type | 
| 217 |  |  |  |  |  |  | #	send_id | 
| 218 |  |  |  |  |  |  | #	return_type | 
| 219 |  |  |  |  |  |  | #	return_id | 
| 220 |  |  |  |  |  |  | #	wet_effect_chain => ec_index, | 
| 221 |  |  |  |  |  |  | #   dry_effect_chain => ec_index, | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 0 |  |  |  |  |  | $hash | 
| 224 | 0 |  |  |  |  |  | } @{$vals{inserts_data}} | 
|  | 0 |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | ]; | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | #say Audio::Nama::json_out($vals{inserts_data}) if $vals{inserts_data}; | 
| 229 |  |  |  |  |  |  | } | 
| 230 | 0 |  |  |  |  |  | my $object = bless { %vals }, $class; | 
| 231 | 0 |  |  |  |  |  | $by_index{$vals{n}} = $object; | 
| 232 | 0 |  |  | 0 |  |  | logpkg(__FILE__,__LINE__,'debug',sub{$object->dump}); | 
|  | 0 |  |  |  |  |  |  | 
| 233 | 0 |  |  |  |  |  | $object; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  | sub AUTOLOAD { | 
| 236 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 237 | 0 |  |  |  |  |  | my ($call) = $AUTOLOAD =~ /([^:]+)$/; | 
| 238 |  |  |  |  |  |  | return $self->{attrib}->{$call} if exists $self->{attrib}->{$call} | 
| 239 | 0 | 0 | 0 |  |  |  | or $is_attribute{$call}; | 
| 240 | 0 |  |  |  |  |  | croak "Autoload fell through. Object type: ", (ref $self), ", illegal method call: $call\n"; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | ### apply effect chain to the specified track | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | sub add_ops { | 
| 246 | 0 |  |  | 0 | 0 |  | my($self, $track, $ec_args) = @_; | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | # Higher priority: track argument | 
| 249 |  |  |  |  |  |  | # Lower priority:  effect chain's own track name attribute | 
| 250 | 0 | 0 | 0 |  |  |  | $track ||= $tn{$self->track_name} if $tn{$self->track_name}; | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | # make sure surname is unique | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 0 |  |  |  |  |  | my ($new_surname, $existing) = $track->unique_surname($ec_args->{surname}); | 
| 256 | 0 | 0 |  |  |  |  | if ( $new_surname ne $ec_args->{surname}) | 
| 257 |  |  |  |  |  |  | { | 
| 258 | 0 |  |  |  |  |  | Audio::Nama::pager_newline( | 
| 259 |  |  |  |  |  |  | "track ". | 
| 260 |  |  |  |  |  |  | $track->name.qq(: other effects with surname "$ec_args->{surname}" found,), | 
| 261 |  |  |  |  |  |  | qq(using "$new_surname". Others are: $existing.)); | 
| 262 | 0 |  |  |  |  |  | $ec_args->{surname} = $new_surname; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  |  | 
| 266 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug',$track->name, | 
| 267 |  |  |  |  |  |  | qq(: adding effect chain ), $self->name, Dumper $self | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | ); | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | # Exclude restoring vol/pan for track_caching. | 
| 272 |  |  |  |  |  |  | # (This conditional is a hack that would be better | 
| 273 |  |  |  |  |  |  | # implemented by subclassing EffectChain | 
| 274 |  |  |  |  |  |  | # for cache/uncache) | 
| 275 |  |  |  |  |  |  |  | 
| 276 | 0 |  |  |  |  |  | my @ops_list; | 
| 277 |  |  |  |  |  |  | my @added; | 
| 278 | 0 | 0 |  |  |  |  | if( $self->track_cache ){ | 
| 279 | 0 | 0 |  |  |  |  | @ops_list = grep{ $_ ne $track->vol and $_ ne $track->pan } | 
| 280 | 0 |  |  |  |  |  | @{$self->ops_list} | 
|  | 0 |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | } else { | 
| 282 | 0 |  |  |  |  |  | @ops_list = @{$self->ops_list}; | 
|  | 0 |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  | map | 
| 285 |  |  |  |  |  |  | { | 
| 286 | 0 |  |  |  |  |  | my $args = | 
|  | 0 |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | { | 
| 288 |  |  |  |  |  |  | chain  		=> $track->n, | 
| 289 |  |  |  |  |  |  | type   		=> $self->type($_), | 
| 290 |  |  |  |  |  |  | params 		=> $self->params($_), | 
| 291 |  |  |  |  |  |  | parent		=> $self->parent_id($_), | 
| 292 |  |  |  |  |  |  | }; | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | # drop the ID if it is already used | 
| 296 | 0 | 0 |  |  |  |  | $args->{id} = $_ unless fxn($_); | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug',"args ", json_out($args)); | 
| 299 |  |  |  |  |  |  |  | 
| 300 | 0 | 0 |  |  |  |  | $args->{surname} = $ec_args->{surname} if $ec_args->{surname}; | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 0 |  |  |  |  |  | my $FX = append_effect($args)->[0]; | 
| 303 | 0 |  |  |  |  |  | push @added, $FX; | 
| 304 | 0 |  |  |  |  |  | my $new_id = $FX->id; | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # the effect ID may be new, or it may be previously | 
| 307 |  |  |  |  |  |  | # assigned ID, | 
| 308 |  |  |  |  |  |  | # whatever value is supplied is guaranteed | 
| 309 |  |  |  |  |  |  | # to be unique; not to collide with any other effect | 
| 310 |  |  |  |  |  |  |  | 
| 311 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug',"new id: $new_id"); | 
| 312 | 0 |  |  |  |  |  | my $orig_id = $_; | 
| 313 | 0 | 0 |  |  |  |  | if ( $new_id ne $orig_id) | 
| 314 |  |  |  |  |  |  | # re-write all controllers to belong to new id | 
| 315 |  |  |  |  |  |  | { | 
| 316 | 0 |  |  |  |  |  | map{ $self->parent_id($_) =~ s/^$orig_id$/$new_id/  } @{$self->ops_list} | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | } @ops_list; | 
| 320 |  |  |  |  |  |  | \@added | 
| 321 | 0 |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  | sub add_inserts { | 
| 323 | 0 |  |  | 0 | 0 |  | my ($self, $track) = @_; | 
| 324 |  |  |  |  |  |  | map | 
| 325 |  |  |  |  |  |  | { | 
| 326 | 0 |  |  |  |  |  | my $insert_data = dclone($_); # copy so safe to modify | 
| 327 |  |  |  |  |  |  | #say "found insert data:\n",Audio::Nama::json_out($insert_data); | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | # get effect chain indices for wet/dry arms | 
| 330 |  |  |  |  |  |  |  | 
| 331 | 0 |  |  |  |  |  | my $wet_effect_chain = delete $insert_data->{wet_effect_chain}; | 
| 332 | 0 |  |  |  |  |  | my $dry_effect_chain = delete $insert_data->{dry_effect_chain}; | 
| 333 | 0 |  |  |  |  |  | my $class 			 = delete $insert_data->{class}; | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 0 |  |  |  |  |  | $insert_data->{track} = $track->name; | 
| 336 | 0 |  |  |  |  |  | my $insert = $class->new(%$insert_data); | 
| 337 |  |  |  |  |  |  | #$Audio::Nama::by_index{$wet_effect_chain}->add($insert->wet_name, $tn{$insert->wet_name}->vol) | 
| 338 |  |  |  |  |  |  | #$Audio::Nama::by_index{$dry_effect_chain}->add($insert->dry_name, $tn{$insert->dry_name}->vol) | 
| 339 | 0 |  |  |  |  |  | } @{$self->inserts_data}; | 
|  | 0 |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  | sub add_region { | 
| 342 | 0 |  |  | 0 | 0 |  | my ($self, $track) = @_; | 
| 343 |  |  |  |  |  |  | # there is also a check in uncache track | 
| 344 | 0 | 0 |  |  |  |  | Audio::Nama::throw($track->name.": track already has region definition\n", | 
| 345 |  |  |  |  |  |  | "failed to apply region @$self->{region}\n"), return | 
| 346 |  |  |  |  |  |  | if $track->is_region; | 
| 347 |  |  |  |  |  |  | $track->set(region_start => $self->{region}->[0], | 
| 348 | 0 |  |  |  |  |  | region_end	 => $self->{region}->[1]); | 
| 349 |  |  |  |  |  |  | } | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | sub add { | 
| 352 | 0 |  |  | 0 | 0 |  | my ($self, $track, $successor) = @_; | 
| 353 |  |  |  |  |  |  | # TODO stop_do_start should take place at this level | 
| 354 |  |  |  |  |  |  | # possibly reconfiguring engine | 
| 355 | 0 |  |  |  |  |  | my $args = {}; | 
| 356 | 0 |  |  |  |  |  | $args->{before} = $successor; | 
| 357 | 0 | 0 |  |  |  |  | $args->{surname} = $self->name if $self->name; | 
| 358 | 0 |  |  |  |  |  | my $added = $self->add_ops($track, $args); | 
| 359 | 0 |  |  |  |  |  | $self->add_inserts($track); | 
| 360 | 0 | 0 |  |  |  |  | $self->add_region($track) if $self->region; | 
| 361 | 0 |  |  |  |  |  | $added | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  | sub destroy { | 
| 365 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 366 | 0 |  |  |  |  |  | delete $by_index{$self->n}; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | #### class routines | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | sub find { | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | # find(): search for an effect chain by attributes | 
| 374 |  |  |  |  |  |  | # | 
| 375 |  |  |  |  |  |  | # Returns EffectChain objects in list context, | 
| 376 |  |  |  |  |  |  | # number of matches in scalar context. | 
| 377 |  |  |  |  |  |  |  | 
| 378 | 0 |  |  | 0 | 0 |  | my %args = @_; | 
| 379 | 0 |  |  |  |  |  | my $unique = delete $args{unique}; | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | # first check for a specified index that matches | 
| 382 |  |  |  |  |  |  | # an existing chain | 
| 383 |  |  |  |  |  |  |  | 
| 384 | 0 | 0 |  |  |  |  | return $by_index{$args{n}} if $args{n}; | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | # otherwise all specified fields must match | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | my @found = grep | 
| 389 | 0 |  |  |  |  |  | { 	my $fx_chain = $_; | 
|  | 0 |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | # check if any specified fields *don't* match | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | my @non_matches = grep | 
| 394 |  |  |  |  |  |  | { | 
| 395 |  |  |  |  |  |  |  | 
| 396 | 0 |  |  |  |  |  | ! ($fx_chain->{attrib}->{$_} eq $args{$_}) | 
|  | 0 |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | #! ($_ ne 'version' and $args{$_} eq 1 and $fx_chain->$_) | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | } keys %args; | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | # if no non-matches, then all have matched, | 
| 403 |  |  |  |  |  |  | # and we return true | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | ! scalar @non_matches | 
| 406 |  |  |  |  |  |  |  | 
| 407 | 0 |  |  |  |  |  | } values %by_index; | 
| 408 |  |  |  |  |  |  |  | 
| 409 | 0 | 0 | 0 |  |  |  | warn("unique chain requested but multiple chains found. Skipping.\n"), | 
| 410 |  |  |  |  |  |  | return if $unique and @found > 1; | 
| 411 |  |  |  |  |  |  |  | 
| 412 | 0 | 0 |  |  |  |  | if( wantarray() ){ $unique ? pop @found : sort{ $a->n cmp $b->n } @found  } | 
|  | 0 | 0 |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 413 | 0 |  |  |  |  |  | else { scalar @found } | 
| 414 |  |  |  |  |  |  | } | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | sub summary { | 
| 417 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 418 | 0 |  |  |  |  |  | my @output; | 
| 419 | 0 | 0 |  |  |  |  | push @output, "  name: ".$self->name if $self->name; | 
| 420 | 0 | 0 |  |  |  |  | push @output, "  track name: ".$self->track_name if $self->track_name; | 
| 421 |  |  |  |  |  |  | push @output, | 
| 422 |  |  |  |  |  |  | map{ | 
| 423 | 0 |  |  |  |  |  | my $i = Audio::Nama::effect_index( $self->{ops_data}->{$_}->{type} ); | 
| 424 | 0 |  |  |  |  |  | my $name = "    ". $fx_cache->{registry}->[$i]->{name}; | 
| 425 | 0 |  |  |  |  |  | } @{$_->ops_list}; | 
|  | 0 |  |  |  |  |  |  | 
| 426 | 0 |  |  |  |  |  | map{ $_,"\n"} @output; | 
|  | 0 |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | } | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | sub move_attributes { | 
| 430 | 0 |  |  | 0 | 0 |  | my $ec_hash = shift; | 
| 431 | 0 |  |  |  |  |  | map { $ec_hash->{attrib}->{$_} = delete $ec_hash->{$_}  } | 
| 432 | 0 |  |  |  |  |  | grep{ $ec_hash->{$_} } | 
|  | 0 |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | @attributes; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  | 0 |  |  | sub DESTROY {} | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  | { | 
| 440 |  |  |  |  |  |  | ####  Effect-chain and -profile routines | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | package Audio::Nama; | 
| 443 |  |  |  |  |  |  | sub add_effect_chain { | 
| 444 | 0 |  |  | 0 |  |  | my ($name, $track, $successor) = @_; | 
| 445 | 0 |  |  |  |  |  | my ($ec) = Audio::Nama::EffectChain::find( | 
| 446 |  |  |  |  |  |  | unique => 1, | 
| 447 |  |  |  |  |  |  | user   => 1, | 
| 448 |  |  |  |  |  |  | name   => $name, | 
| 449 |  |  |  |  |  |  | ); | 
| 450 | 0 | 0 |  |  |  |  | if( $ec ){ $ec->add($Audio::Nama::this_track, $successor) } | 
|  | 0 |  |  |  |  |  |  | 
| 451 | 0 |  |  |  |  |  | else { Audio::Nama::throw("$name: effect chain not found") } | 
| 452 | 0 |  |  |  |  |  | 1; | 
| 453 |  |  |  |  |  |  | } | 
| 454 |  |  |  |  |  |  | sub new_effect_profile { | 
| 455 | 0 |  |  | 0 |  |  | logsub("&new_effect_profile"); | 
| 456 | 0 |  |  |  |  |  | my ($bunch, $profile) = @_; | 
| 457 | 0 |  |  |  |  |  | my @tracks = bunch_tracks($bunch); | 
| 458 | 0 |  |  |  |  |  | Audio::Nama::pager( qq(effect profile "$profile" created for tracks: @tracks) ); | 
| 459 |  |  |  |  |  |  | map { | 
| 460 | 0 |  |  |  |  |  | Audio::Nama::EffectChain->new( | 
| 461 |  |  |  |  |  |  | profile 	=> $profile, | 
| 462 |  |  |  |  |  |  | user		=> 1, | 
| 463 |  |  |  |  |  |  | global		=> 1, | 
| 464 |  |  |  |  |  |  | track_name	=> $_, | 
| 465 |  |  |  |  |  |  | ops_list	=> [ $tn{$_}->fancy_ops ], | 
| 466 | 0 |  |  |  |  |  | inserts_data => $tn{$_}->inserts, | 
| 467 |  |  |  |  |  |  | ); | 
| 468 |  |  |  |  |  |  | } @tracks; | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  | sub delete_effect_profile { | 
| 471 | 0 |  |  | 0 |  |  | logsub("&delete_effect_profile"); | 
| 472 | 0 |  |  |  |  |  | my $name = shift; | 
| 473 | 0 |  |  |  |  |  | Audio::Nama::pager( qq(deleting effect profile: $name) ); | 
| 474 | 0 |  |  |  |  |  | map{ $_->destroy} Audio::Nama::EffectChain::find( profile => $name ); | 
|  | 0 |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | } | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | sub apply_effect_profile {  # overwriting current effects | 
| 478 | 0 |  |  | 0 |  |  | logsub("&apply_effect_profile"); | 
| 479 | 0 |  |  |  |  |  | my ($profile) = @_; | 
| 480 | 0 |  |  |  |  |  | my @chains = Audio::Nama::EffectChain::find(profile => $profile); | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | # add missing tracks | 
| 483 | 0 |  |  |  |  |  | map{ Audio::Nama::pager( "adding track $_" ); add_track($_) } | 
|  | 0 |  |  |  |  |  |  | 
| 484 | 0 |  |  |  |  |  | grep{ !$tn{$_} } | 
| 485 | 0 |  |  |  |  |  | map{ $_->track_name } @chains; | 
|  | 0 |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | # add effect chains | 
| 487 | 0 |  |  |  |  |  | map{ $_->add } @chains; | 
|  | 0 |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  | sub is_effect_chain { | 
| 490 | 0 |  |  | 0 |  |  | my $name = shift; | 
| 491 | 0 |  |  |  |  |  | my ($fxc) = Audio::Nama::EffectChain::find(name => $name, unique => 1); | 
| 492 | 0 |  |  |  |  |  | $fxc | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  | 1; | 
| 496 |  |  |  |  |  |  | __END__ |