| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ---------- Track ----------- | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | package Audio::Nama; | 
| 4 |  |  |  |  |  |  | { | 
| 5 |  |  |  |  |  |  | package Audio::Nama::Track; | 
| 6 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 549 |  | 
| 7 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Log qw(logpkg logsub); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 69 |  | 
| 8 | 1 |  |  | 1 |  | 675 | use Audio::Nama::Effect  qw(fxn); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 71 |  | 
| 9 | 1 |  |  | 1 |  | 7 | use List::MoreUtils qw(first_index); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 10 | 1 |  |  | 1 |  | 391 | use Try::Tiny; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 11 | 1 |  |  | 1 |  | 5 | use Modern::Perl; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 13 |  | 
| 12 | 1 |  |  | 1 |  | 122 | use Carp qw(carp cluck croak); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 55 |  | 
| 13 | 1 |  |  | 1 |  | 934 | use File::Copy qw(copy); | 
|  | 1 |  |  |  |  | 2435 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 14 | 1 |  |  | 1 |  | 5 | use File::Slurp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 68 |  | 
| 15 | 1 |  |  | 1 |  | 977 | use Memoize qw(memoize unmemoize); | 
|  | 1 |  |  |  |  | 2526 |  | 
|  | 1 |  |  |  |  | 61 |  | 
| 16 | 1 |  |  | 1 |  | 5 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 51 |  | 
| 17 |  |  |  |  |  |  | our $VERSION = 1.0; | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Util qw(freq input_node dest_type dest_string join_path); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 67 |  | 
| 20 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Assign qw(json_out); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 21 | 1 |  |  | 1 |  | 5 | use vars qw($n %by_name @by_index %track_names %by_index); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 82 |  | 
| 22 | 1 |  |  |  |  | 9 | use Audio::Nama::Object qw( | 
| 23 |  |  |  |  |  |  | class | 
| 24 |  |  |  |  |  |  | is_mix_track | 
| 25 |  |  |  |  |  |  | n | 
| 26 |  |  |  |  |  |  | name | 
| 27 |  |  |  |  |  |  | group | 
| 28 |  |  |  |  |  |  | rw | 
| 29 |  |  |  |  |  |  | version | 
| 30 |  |  |  |  |  |  | width | 
| 31 |  |  |  |  |  |  | ops | 
| 32 |  |  |  |  |  |  | vol | 
| 33 |  |  |  |  |  |  | pan | 
| 34 |  |  |  |  |  |  | fader | 
| 35 |  |  |  |  |  |  | latency_op | 
| 36 |  |  |  |  |  |  | offset | 
| 37 |  |  |  |  |  |  | old_vol_level | 
| 38 |  |  |  |  |  |  | old_pan_level | 
| 39 |  |  |  |  |  |  | playat | 
| 40 |  |  |  |  |  |  | region_start | 
| 41 |  |  |  |  |  |  | region_end | 
| 42 |  |  |  |  |  |  | modifiers | 
| 43 |  |  |  |  |  |  | looping | 
| 44 |  |  |  |  |  |  | hide | 
| 45 |  |  |  |  |  |  | source_id | 
| 46 |  |  |  |  |  |  | source_type | 
| 47 |  |  |  |  |  |  | send_id | 
| 48 |  |  |  |  |  |  | send_type | 
| 49 |  |  |  |  |  |  | target | 
| 50 |  |  |  |  |  |  | project | 
| 51 |  |  |  |  |  |  | comment | 
| 52 |  |  |  |  |  |  | version_comment | 
| 53 |  |  |  |  |  |  | forbid_user_ops | 
| 54 |  |  |  |  |  |  | engine_group | 
| 55 |  |  |  |  |  |  | current_edit | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 1 |  |  | 1 |  | 5 | ); | 
|  | 1 |  |  |  |  | 1 |  | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | # Note that ->vol return the effect_id | 
| 60 |  |  |  |  |  |  | # ->old_volume_level is the level saved before muting | 
| 61 |  |  |  |  |  |  | # ->old_pan_level is the level saved before pan full right/left | 
| 62 |  |  |  |  |  |  | # commands | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | initialize(); | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | ### class subroutines | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | sub initialize { | 
| 71 | 1 |  |  | 1 | 0 | 3 | $n = 0; 	# incrementing numeric key | 
| 72 | 1 |  |  |  |  | 2 | %by_index = ();	# return ref to Track by numeric key | 
| 73 | 1 |  |  |  |  | 2 | %by_name = ();	# return ref to Track by name | 
| 74 | 1 |  |  |  |  | 3 | %track_names = (); | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub idx { # return first free track index | 
| 78 | 0 |  |  | 0 | 0 |  | my $n = 0; | 
| 79 | 0 |  |  |  |  |  | while (++$n){ | 
| 80 | 0 | 0 |  |  |  |  | return $n if not $by_index{$n} | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  | sub new { | 
| 84 |  |  |  |  |  |  | # returns a reference to an object | 
| 85 |  |  |  |  |  |  | # | 
| 86 |  |  |  |  |  |  | # tracks are indexed by: | 
| 87 |  |  |  |  |  |  | # (1) name and | 
| 88 |  |  |  |  |  |  | # (2) by an assigned index that is used as chain_id | 
| 89 |  |  |  |  |  |  | #     the index may be supplied as a parameter | 
| 90 |  |  |  |  |  |  | # | 
| 91 |  |  |  |  |  |  | # | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 0 |  |  | 0 | 0 |  | my $class = shift; | 
| 94 | 0 |  |  |  |  |  | my %vals = @_; | 
| 95 | 0 |  |  |  |  |  | my $novol = delete $vals{novol}; | 
| 96 | 0 |  |  |  |  |  | my $nopan = delete $vals{nopan}; | 
| 97 | 0 |  |  |  |  |  | my $restore = delete $vals{restore}; | 
| 98 | 0 | 0 |  |  |  |  | say "restoring track $vals{name}" if $restore; | 
| 99 | 0 |  |  |  |  |  | my @undeclared = grep{ ! $_is_field{$_} } keys %vals; | 
|  | 0 |  |  |  |  |  |  | 
| 100 | 0 | 0 |  |  |  |  | croak "undeclared field: @undeclared" if @undeclared; | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | # silently return if track already exists | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 0 | 0 |  |  |  |  | return if $by_name{$vals{name}}; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 0 |  | 0 |  |  |  | my $n = $vals{n} || idx(); | 
| 107 | 0 |  |  |  |  |  | my $object = bless { | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | ## 		defaults ## | 
| 111 |  |  |  |  |  |  | class	=> $class, | 
| 112 |  |  |  |  |  |  | name 	=> "Audio_$n", | 
| 113 |  |  |  |  |  |  | group	=> 'Main', | 
| 114 |  |  |  |  |  |  | n    	=> $n, | 
| 115 |  |  |  |  |  |  | ops     => [], | 
| 116 |  |  |  |  |  |  | width => 1, | 
| 117 |  |  |  |  |  |  | vol  	=> undef, | 
| 118 |  |  |  |  |  |  | pan 	=> undef, | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | modifiers 		=> q(), # start, reverse, audioloop, playat | 
| 121 |  |  |  |  |  |  | looping 		=> undef, # do we repeat our sound sample | 
| 122 |  |  |  |  |  |  | source_type 	=> q(soundcard), | 
| 123 |  |  |  |  |  |  | source_id   	=> "1", | 
| 124 |  |  |  |  |  |  | send_type 		=> undef, | 
| 125 |  |  |  |  |  |  | send_id   		=> undef, | 
| 126 |  |  |  |  |  |  | old_vol_level	=> undef, | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | @_ 			}, $class; | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 0 |  |  |  |  |  | $track_names{$vals{name}}++; | 
| 131 | 0 |  |  |  |  |  | $by_index{$n} = $object; | 
| 132 | 0 |  |  |  |  |  | $by_name{ $object->name } = $object; | 
| 133 | 0 | 0 | 0 |  |  |  | Audio::Nama::add_pan_control($n) unless $nopan or $restore; | 
| 134 | 0 | 0 | 0 |  |  |  | Audio::Nama::add_volume_control($n) unless $novol or $restore; | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  |  |  |  | $Audio::Nama::this_track = $object; | 
| 137 | 0 | 0 |  |  |  |  | $Audio::Nama::ui->track_gui($object->n) unless $object->hide; | 
| 138 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug',$object->name, ": ","newly created track",$/,json_out($object->as_hash)); | 
| 139 | 0 |  |  |  |  |  | $object; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | ### object methods | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | # TODO these conditional clauses should be separated | 
| 146 |  |  |  |  |  |  | # into classes | 
| 147 |  |  |  |  |  |  | sub dir { | 
| 148 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 149 | 0 | 0 |  |  |  |  | $self->project | 
| 150 |  |  |  |  |  |  | ? join_path(Audio::Nama::project_root(), $self->project, '.wav') | 
| 151 |  |  |  |  |  |  | : Audio::Nama::this_wav_dir(); | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | sub basename { | 
| 155 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 156 | 0 | 0 |  |  |  |  | $self->target || $self->name | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 | 0 |  |  | 0 | 0 |  | sub full_path { my $track = shift; join_path($track->dir, $track->current_wav) } | 
|  | 0 |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | sub group_last { | 
| 162 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 163 | 0 |  |  |  |  |  | my $bus = $bn{$track->group}; | 
| 164 | 0 |  |  |  |  |  | $bus->last; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub last { $_[0]->versions->[-1] || 0 } | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | sub current_wav { | 
| 170 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 171 | 0 |  |  |  |  |  | my $last = $track->current_version; | 
| 172 | 0 | 0 |  |  |  |  | if 	($track->rec_status eq REC){ | 
|  |  | 0 |  |  |  |  |  | 
| 173 | 0 |  |  |  |  |  | $track->name . '_' . $last . '.wav' | 
| 174 |  |  |  |  |  |  | } elsif ( $track->rec_status eq PLAY){ | 
| 175 | 0 |  |  |  |  |  | my $filename = $track->targets->{ $track->monitor_version } ; | 
| 176 | 0 |  |  |  |  |  | $filename | 
| 177 |  |  |  |  |  |  | } else { | 
| 178 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "track ", $track->name, ": no current version") ; | 
| 179 | 0 |  |  |  |  |  | undef; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | sub current_version { | 
| 184 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 185 | 0 |  |  |  |  |  | my $status = $track->rec_status; | 
| 186 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', "last: $last status: $status"); | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | # two possible version numbers, depending on REC/PLAY status | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 0 | 0 |  |  |  |  | if 	($status eq REC) | 
|  |  | 0 |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | { | 
| 192 |  |  |  |  |  |  | my $last = $config->{use_group_numbering} | 
| 193 | 0 | 0 |  |  |  |  | ? Audio::Nama::Bus::overall_last() | 
| 194 |  |  |  |  |  |  | : $track->last; | 
| 195 | 0 |  |  |  |  |  | return ++$last | 
| 196 |  |  |  |  |  |  | } | 
| 197 | 0 |  |  |  |  |  | elsif ( $status eq PLAY){ return $track->monitor_version } | 
| 198 | 0 |  |  |  |  |  | else { return 0 } | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub monitor_version { | 
| 202 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 0 |  |  |  |  |  | my $bus = $bn{$track->group}; | 
| 205 |  |  |  |  |  |  | return $track->version if $track->version | 
| 206 | 0 | 0 | 0 |  |  |  | and grep {$track->version  == $_ } @{$track->versions} ; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 207 | 0 |  |  |  |  |  | $track->last; | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | sub maybe_monitor { # ordinary sub, not object method | 
| 211 | 0 |  |  | 0 | 0 |  | my $monitor_version = shift; | 
| 212 | 0 | 0 | 0 |  |  |  | return PLAY if $monitor_version and ! $mode->doodle; | 
| 213 | 0 |  |  |  |  |  | return OFF; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | sub targets { # WAV file targets, distinct from 'target' attribute | 
| 217 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 218 | 0 |  |  |  |  |  | Audio::Nama::Wav::targets(dir => $self->dir, name => $self->basename) | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  | sub versions { | 
| 221 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 222 | 0 |  |  |  |  |  | Audio::Nama::Wav::versions(dir => $self->dir, name => $self->basename) | 
| 223 |  |  |  |  |  |  | } | 
| 224 |  |  |  |  |  |  | sub last { | 
| 225 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 226 | 0 | 0 |  |  |  |  | Audio::Nama::Wav::last(dir => $self->dir, name => $self->basename) || 0 | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  | # if you belong to a bus with an opinion, go that way | 
| 229 |  |  |  |  |  |  | sub engine_group { | 
| 230 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 231 | 0 |  |  |  |  |  | my $bus = $bn{$track->group}; | 
| 232 | 0 | 0 | 0 |  |  |  | $bus->engine_group || $track->{engine_group} || 'Nama' | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | sub engine { | 
| 235 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 236 | 0 |  |  |  |  |  | $en{$track->engine_group} | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  | sub rec_status { | 
| 239 |  |  |  |  |  |  | #	logsub("&rec_status"); | 
| 240 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | #my $source_id = $track->source_id; | 
| 243 | 0 |  |  |  |  |  | my $monitor_version = $track->monitor_version; | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 0 |  |  |  |  |  | my $bus = $bn{$track->group}; | 
| 246 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', join " ", "bus:",$bus->name, $bus->rw); | 
| 247 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "track: $track->{name}, source: $track->{source_id}, monitor version: $monitor_version"); | 
| 248 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', "track: ", $track->name, ", source: ", | 
| 249 |  |  |  |  |  |  | #	$track->source_id, ", monitor version: $monitor_version"); | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | # first, check for conditions resulting in status OFF | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 0 | 0 | 0 |  |  |  | if ( $bus->rw eq OFF | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 254 |  |  |  |  |  |  | or $track->rw eq OFF | 
| 255 |  |  |  |  |  |  | or $mode->doodle and ! $mode->eager and $track->rw eq REC and | 
| 256 |  |  |  |  |  |  | $setup->{tracks_with_duplicate_inputs}->{$track->name} | 
| 257 |  |  |  |  |  |  | or $track->engine_group ne $Audio::Nama::this_engine->name | 
| 258 | 0 |  |  |  |  |  | ){ 	return			  OFF } | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | # having reached here, we know $bus->rw and $track->rw are REC or PLAY | 
| 261 |  |  |  |  |  |  | # so the result will be REC or PLAY if conditions are met | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # second, set REC status if possible | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 0 | 0 |  |  |  |  | if( $track->rw eq REC){ | 
|  |  | 0 |  |  |  |  |  | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 0 |  |  |  |  |  | my $source_type = $track->source_type; | 
| 268 | 0 | 0 | 0 |  |  |  | if ($source_type eq 'track' or $source_type eq 'loop'){ return REC } | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | elsif ($source_type eq 'jack_client'){ | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | # we expect an existing JACK client that | 
| 272 |  |  |  |  |  |  | # *outputs* a signal for our track input | 
| 273 |  |  |  |  |  |  |  | 
| 274 | 0 | 0 |  |  |  |  | Audio::Nama::jack_client_array($track->source_id,'output') | 
| 275 |  |  |  |  |  |  | ?  return REC | 
| 276 |  |  |  |  |  |  | :  return OFF | 
| 277 |  |  |  |  |  |  | } | 
| 278 | 0 |  |  |  |  |  | elsif ($source_type eq 'jack_manual'){ return REC } | 
| 279 | 0 |  |  |  |  |  | elsif ($source_type eq 'jack_ports_list'){ return REC } | 
| 280 | 0 |  |  |  |  |  | elsif ($source_type eq 'null')	{ return REC } | 
| 281 | 0 |  |  |  |  |  | elsif ($source_type eq 'rtnull')	{ return REC } | 
| 282 | 0 |  |  |  |  |  | elsif ($source_type eq 'soundcard'){ return REC } | 
| 283 | 0 |  |  |  |  |  | elsif ($source_type eq 'bus')	{ return REC } # maybe $track->rw ?? | 
| 284 | 0 |  |  |  |  |  | else { return OFF } | 
| 285 |  |  |  |  |  |  | } | 
| 286 | 0 |  |  |  |  |  | elsif( $track->rw eq MON){ MON } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | # set PLAY status if possible | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 0 |  |  |  |  |  | else { 			maybe_monitor($monitor_version) | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | sub rec_status_display { | 
| 295 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 296 | 0 |  |  |  |  |  | my $rs = $track->rec_status; | 
| 297 | 0 |  |  |  |  |  | my $status; | 
| 298 | 0 |  |  |  |  |  | $status .= $rs; | 
| 299 | 0 | 0 |  |  |  |  | $status .= ' v'.$track->current_version if $rs eq REC; | 
| 300 | 0 |  |  |  |  |  | $status | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  | # these settings will only affect WAV playback | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | sub region_start_time { | 
| 305 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 306 |  |  |  |  |  |  | #return if $track->rec_status ne PLAY; | 
| 307 | 0 | 0 |  |  |  |  | carp $track->name, ": expected PLAY status" if $track->rec_status ne PLAY; | 
| 308 | 0 |  |  |  |  |  | Audio::Nama::Mark::time_from_tag( $track->region_start ) | 
| 309 |  |  |  |  |  |  | } | 
| 310 |  |  |  |  |  |  | sub region_end_time { | 
| 311 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 312 |  |  |  |  |  |  | #return if $track->rec_status ne PLAY; | 
| 313 | 0 | 0 |  |  |  |  | carp $track->name, ": expected PLAY status" if $track->rec_status ne PLAY; | 
| 314 | 0 | 0 |  |  |  |  | if ( $track->region_end eq 'END' ){ | 
| 315 | 0 |  |  |  |  |  | return $track->wav_length; | 
| 316 |  |  |  |  |  |  | } else { | 
| 317 | 0 |  |  |  |  |  | Audio::Nama::Mark::time_from_tag( $track->region_end ) | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  | sub playat_time { | 
| 321 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 322 | 0 | 0 |  |  |  |  | carp $track->name, ": expected PLAY status" if $track->rec_status ne PLAY; | 
| 323 |  |  |  |  |  |  | #return if $track->rec_status ne PLAY; | 
| 324 | 0 |  |  |  |  |  | Audio::Nama::Mark::time_from_tag( $track->playat ) | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | # the following methods adjust | 
| 328 |  |  |  |  |  |  | # region start and playat values during edit mode | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | sub shifted_region_start_time { | 
| 331 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 332 | 0 | 0 |  |  |  |  | return $track->region_start_time unless $mode->{offset_run}; | 
| 333 | 0 |  |  |  |  |  | Audio::Nama::new_region_start(Audio::Nama::edit_vars($track)); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  | sub shifted_playat_time { | 
| 337 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 338 | 0 | 0 |  |  |  |  | return $track->playat_time unless $mode->{offset_run}; | 
| 339 | 0 |  |  |  |  |  | Audio::Nama::new_playat(Audio::Nama::edit_vars($track)); | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  | sub shifted_region_end_time { | 
| 342 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 343 | 0 | 0 |  |  |  |  | return $track->region_end_time unless $mode->{offset_run}; | 
| 344 | 0 |  |  |  |  |  | Audio::Nama::new_region_end(Audio::Nama::edit_vars($track)); | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | sub region_is_out_of_bounds { | 
| 348 | 0 | 0 |  | 0 | 0 |  | return unless $mode->{offset_run}; | 
| 349 | 0 |  |  |  |  |  | my $track = shift; | 
| 350 | 0 |  |  |  |  |  | Audio::Nama::case(Audio::Nama::edit_vars($track)) =~ /out_of_bounds/ | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | sub fancy_ops { # returns list | 
| 354 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 355 | 0 |  |  |  |  |  | my @skip = 	grep {Audio::Nama::fxn($_)}  # must exist | 
| 356 | 0 |  |  |  |  |  | map { $track->{$_} } qw(vol pan fader latency_op ); | 
|  | 0 |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | # make a dictionary of ops to exclude | 
| 359 |  |  |  |  |  |  | # that includes utility ops and their controllers | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 0 |  |  |  |  |  | my %skip; | 
| 362 |  |  |  |  |  |  |  | 
| 363 | 0 |  |  |  |  |  | map{ $skip{$_}++ } @skip, Audio::Nama::expanded_ops_list(@skip); | 
|  | 0 |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 0 | 0 |  |  |  |  | grep{ ! $skip{$_} } @{ $track->{ops} || [] }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  | sub fancy_ops_o { | 
| 368 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 369 | 0 |  |  |  |  |  | map{ Audio::Nama::fxn($_) } $track->fancy_ops(); | 
|  | 0 |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | sub snapshot { | 
| 373 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 374 | 0 |  |  |  |  |  | my $fields = shift; | 
| 375 | 0 |  |  |  |  |  | my %snap; | 
| 376 | 0 |  |  |  |  |  | my $i = 0; | 
| 377 | 0 |  |  |  |  |  | for(@$fields){ | 
| 378 | 0 |  |  |  |  |  | $snap{$_} = $track->$_; | 
| 379 |  |  |  |  |  |  | } | 
| 380 | 0 |  |  |  |  |  | \%snap; | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | # create an edge representing sound source | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | sub input_path { | 
| 387 |  |  |  |  |  |  |  | 
| 388 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | # the corresponding bus handles input routing for mix tracks | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | # bus mix tracks don't usually need to be connected | 
| 393 | 0 | 0 | 0 |  |  |  | return() if $track->is_mix_track and $track->rec_status ne PLAY; | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | # the track may route to: | 
| 396 |  |  |  |  |  |  | # + another track | 
| 397 |  |  |  |  |  |  | # + an external source (soundcard or JACK client) | 
| 398 |  |  |  |  |  |  | # + a WAV file | 
| 399 |  |  |  |  |  |  |  | 
| 400 | 0 | 0 | 0 |  |  |  | if($track->source_type eq 'track'){ ($track->source_id, $track->name) } | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | elsif($track->rec_status =~ /REC|MON/){ | 
| 403 | 0 |  |  |  |  |  | (input_node($track->source_type), $track->name) } | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | elsif($track->rec_status eq PLAY and ! $mode->doodle){ | 
| 406 | 0 |  |  |  |  |  | ('wav_in', $track->name) | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  | } | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 0 | 0 |  | 0 | 0 |  | sub has_insert  { $_[0]->prefader_insert or $_[0]->postfader_insert } | 
| 412 |  |  |  |  |  |  |  | 
| 413 | 0 |  |  | 0 | 0 |  | sub prefader_insert { Audio::Nama::Insert::get_id($_[0],'pre') } | 
| 414 | 0 |  |  | 0 | 0 |  | sub postfader_insert { Audio::Nama::Insert::get_id($_[0],'post') } | 
| 415 |  |  |  |  |  |  | sub inserts {  [  # return array ref | 
| 416 | 0 |  |  |  |  |  | map{ $Audio::Nama::Insert::by_index{$_} }grep{$_} | 
|  | 0 |  |  |  |  |  |  | 
| 417 | 0 |  |  | 0 | 0 |  | map{ Audio::Nama::Insert::get_id($_[0],$_)} qw(pre post) | 
|  | 0 |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | ] | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # remove track object and all effects | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | sub remove { | 
| 424 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 425 | 0 |  |  |  |  |  | my $n = $track->n; | 
| 426 | 0 |  |  |  |  |  | $ui->remove_track_gui($n); | 
| 427 |  |  |  |  |  |  | # remove corresponding fades | 
| 428 | 0 |  |  |  |  |  | map{ $_->remove } grep { $_->track eq $track->name } values %Audio::Nama::Fade::by_index; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | # remove effects | 
| 430 | 0 |  |  |  |  |  | map{ Audio::Nama::remove_effect($_) } @{ $track->ops }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 431 | 0 |  |  |  |  |  | delete $by_index{$n}; | 
| 432 | 0 |  |  |  |  |  | delete $by_name{$track->name}; | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | ### object methods for text-based commands | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | # Reasonable behavior whether 'source' and 'send' commands | 
| 438 |  |  |  |  |  |  | # are issued in JACK or ALSA mode. | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 0 |  | 0 | 0 | 0 |  | sub soundcard_channel { $_[0] // 1 } | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | sub set_io { | 
| 443 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 444 | 0 |  |  |  |  |  | my ($direction, $id, $type) = @_; | 
| 445 |  |  |  |  |  |  | # $direction: send | source | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | # unless we are dealing with a simple query, | 
| 448 |  |  |  |  |  |  | # by the end of this routine we are going to assign | 
| 449 |  |  |  |  |  |  | # the following fields using the values in the | 
| 450 |  |  |  |  |  |  | # $type and $id variables: | 
| 451 |  |  |  |  |  |  | # | 
| 452 |  |  |  |  |  |  | #    source_type | 
| 453 |  |  |  |  |  |  | #    source_id | 
| 454 |  |  |  |  |  |  | # | 
| 455 |  |  |  |  |  |  | #    -OR- | 
| 456 |  |  |  |  |  |  | # | 
| 457 |  |  |  |  |  |  | #    send_type | 
| 458 |  |  |  |  |  |  | #    send_id | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  |  | 
| 461 | 0 |  |  |  |  |  | my $type_field = $direction."_type"; | 
| 462 | 0 |  |  |  |  |  | my $id_field   = $direction."_id"; | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | # respond to query | 
| 465 | 0 | 0 |  |  |  |  | if ( ! $id ){ return $track->$type_field ? $track->$id_field : undef } | 
|  | 0 | 0 |  |  |  |  |  | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | # set values, returning new setting | 
| 468 | 0 |  | 0 |  |  |  | $type ||= dest_type( $id ); | 
| 469 |  |  |  |  |  |  |  | 
| 470 | 0 | 0 |  |  |  |  | if( $type eq 'track')		{} | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | elsif( $type eq 'soundcard'){} # no changes needed | 
| 472 |  |  |  |  |  |  | elsif( $type eq 'bus')     	{} # -ditto- | 
| 473 |  |  |  |  |  |  | #elsif( $type eq 'loop')    {}  # unused at present | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | # don't allow user to set JACK I/O unless JACK server is running | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | elsif( $type =~ /jack/ ){ | 
| 478 |  |  |  |  |  |  | Audio::Nama::throw("JACK server not running! " | 
| 479 |  |  |  |  |  |  | ,"Cannot set JACK client or port as track source."), | 
| 480 | 0 | 0 |  |  |  |  | return unless $jack->{jackd_running}; | 
| 481 |  |  |  |  |  |  |  | 
| 482 | 0 | 0 |  |  |  |  | if( $type eq 'jack_manual'){ | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 483 |  |  |  |  |  |  |  | 
| 484 | 0 |  |  |  |  |  | my $port_name = $track->jack_manual_port($direction); | 
| 485 |  |  |  |  |  |  |  | 
| 486 | 0 |  |  |  |  |  | Audio::Nama::pager($track->name, ": JACK $direction port is $port_name. Make connections manually."); | 
| 487 | 0 |  |  |  |  |  | $id = 'manual'; | 
| 488 | 0 |  |  |  |  |  | $id = $port_name; | 
| 489 | 0 |  |  |  |  |  | $type = 'jack_manual'; | 
| 490 |  |  |  |  |  |  | } | 
| 491 |  |  |  |  |  |  | elsif( $type eq 'jack_client'){ | 
| 492 | 0 | 0 |  |  |  |  | my $client_direction = $direction eq 'source' ? 'output' : 'input'; | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 0 |  |  |  |  |  | my $name = $track->name; | 
| 495 | 0 |  |  |  |  |  | my $width = scalar @{ Audio::Nama::jack_client_array($id, $client_direction) }; | 
|  | 0 |  |  |  |  |  |  | 
| 496 | 0 | 0 |  |  |  |  | $width or Audio::Nama::pager( | 
| 497 |  |  |  |  |  |  | qq($name: $direction port for JACK client "$id" not found.)); | 
| 498 | 0 | 0 |  |  |  |  | $width or return; | 
| 499 | 0 | 0 |  |  |  |  | $width ne $track->width and Audio::Nama::pager( | 
| 500 |  |  |  |  |  |  | $track->name, ": track set to ", Audio::Nama::width($track->width), | 
| 501 |  |  |  |  |  |  | qq(, but JACK source "$id" is ), Audio::Nama::width($width), '.'); | 
| 502 |  |  |  |  |  |  | } | 
| 503 |  |  |  |  |  |  | elsif( $type eq 'jack_ports_list' ){ | 
| 504 | 0 |  |  |  |  |  | $id =~ /(\w+)\.ports/; | 
| 505 | 0 |  | 0 |  |  |  | my $ports_file_name = ($1 || $track->name) .  '.ports'; | 
| 506 | 0 |  |  |  |  |  | $id = $ports_file_name; | 
| 507 |  |  |  |  |  |  | # warn if ports do not exist | 
| 508 | 0 | 0 |  |  |  |  | Audio::Nama::throw($track->name, qq(: ports file "$id" not found in ),Audio::Nama::project_root(),". Skipping."), | 
| 509 |  |  |  |  |  |  | return unless -e join_path( Audio::Nama::project_root(), $id ); | 
| 510 |  |  |  |  |  |  | # check if ports file parses | 
| 511 |  |  |  |  |  |  | } | 
| 512 |  |  |  |  |  |  | } | 
| 513 | 0 |  |  |  |  |  | $track->set($type_field => $type); | 
| 514 | 0 |  |  |  |  |  | $track->set($id_field => $id); | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  | sub source { # command for setting, showing track source | 
| 517 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 518 | 0 |  |  |  |  |  | my ($id, $type) = @_; | 
| 519 | 0 |  |  |  |  |  | $track->set_io( 'source', $id, $type); | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  | sub send { # command for setting, showing track source | 
| 522 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 523 | 0 |  |  |  |  |  | my ($id, $type) = @_; | 
| 524 | 0 |  |  |  |  |  | $track->set_io( 'send', $id, $type); | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  | sub set_source { | 
| 527 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 528 | 0 |  |  |  |  |  | my ($source, $type) = @_; | 
| 529 | 0 |  |  |  |  |  | my $old_source = $track->input_object_text; | 
| 530 | 0 |  |  |  |  |  | $track->set_io('source',$source, $type); | 
| 531 | 0 |  |  |  |  |  | my $new_source = $track->input_object_text;; | 
| 532 | 0 |  |  |  |  |  | my $object = $new_source; | 
| 533 | 0 | 0 |  |  |  |  | if ( $old_source  eq $new_source ){ | 
| 534 | 0 |  |  |  |  |  | Audio::Nama::pager($track->name, ": input unchanged, $object"); | 
| 535 |  |  |  |  |  |  | } else { | 
| 536 | 0 |  |  |  |  |  | Audio::Nama::pager("Track ",$track->name, ": source set to $object"); | 
| 537 | 0 | 0 |  |  |  |  | if (transition_to_null($old_source, $new_source)) | 
| 538 |  |  |  |  |  |  | { | 
| 539 | 0 |  |  |  |  |  | Audio::Nama::pager("Track ",$track->name, ": null input, toggling to MON"); | 
| 540 | 0 | 0 |  |  |  |  | $track->set(rw => MON) if $track->rw eq REC; | 
| 541 |  |  |  |  |  |  | } | 
| 542 |  |  |  |  |  |  | } | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  | { | 
| 545 |  |  |  |  |  |  | my $null_re = qr/^\s*(rt)?null\s*$/; | 
| 546 |  |  |  |  |  |  | sub transition_from_null { | 
| 547 | 0 |  |  | 0 | 0 |  | my ($old, $new) = @_; | 
| 548 | 0 | 0 |  |  |  |  | $old =~ /$null_re/ and $new !~ /$null_re/ | 
| 549 |  |  |  |  |  |  | } | 
| 550 |  |  |  |  |  |  | sub transition_to_null { | 
| 551 | 0 |  |  | 0 | 0 |  | my ($old, $new) = @_; | 
| 552 | 0 | 0 |  |  |  |  | $old !~ /$null_re/ and $new =~ /$null_re/ | 
| 553 |  |  |  |  |  |  | } | 
| 554 |  |  |  |  |  |  | } | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | sub set_version { | 
| 557 | 0 |  |  | 0 | 0 |  | my ($track, $n) = @_; | 
| 558 | 0 |  |  |  |  |  | my $name = $track->name; | 
| 559 | 0 | 0 |  |  |  |  | if ($n == 0){ | 
|  |  | 0 |  |  |  |  |  | 
| 560 | 0 |  |  |  |  |  | Audio::Nama::pager("$name: following bus default\n"); | 
| 561 | 0 |  |  |  |  |  | $track->set(version => $n) | 
| 562 | 0 |  |  |  |  |  | } elsif ( grep{ $n == $_ } @{$track->versions} ){ | 
|  | 0 |  |  |  |  |  |  | 
| 563 | 0 |  |  |  |  |  | Audio::Nama::pager("$name: anchoring version $n\n"); | 
| 564 | 0 |  |  |  |  |  | $track->set(version => $n) | 
| 565 |  |  |  |  |  |  | } else { | 
| 566 | 0 |  |  |  |  |  | Audio::Nama::throw("$name: version $n does not exist, skipping.\n") | 
| 567 |  |  |  |  |  |  | } | 
| 568 |  |  |  |  |  |  | } | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | sub set_send { | 
| 571 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 572 | 0 |  |  |  |  |  | my ($output, $type) = @_; | 
| 573 | 0 |  |  |  |  |  | my $old_send = $track->output_object_text; | 
| 574 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "send was $old_send"); | 
| 575 | 0 |  |  |  |  |  | $track->send($output, $type); | 
| 576 | 0 |  |  |  |  |  | my $new_send = $track->output_object_text; | 
| 577 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "send is now $new_send"); | 
| 578 | 0 |  |  |  |  |  | my $object = $track->output_object_text; | 
| 579 | 0 | 0 |  |  |  |  | if ( $old_send  eq $new_send ){ | 
| 580 | 0 | 0 |  |  |  |  | Audio::Nama::pager("Track ",$track->name, ": send unchanged, ", | 
| 581 |  |  |  |  |  |  | ( $object ?  $object : 'off')); | 
| 582 |  |  |  |  |  |  | } else { | 
| 583 | 0 | 0 |  |  |  |  | Audio::Nama::pager("Track ",$track->name, ": ", | 
| 584 |  |  |  |  |  |  | $object | 
| 585 |  |  |  |  |  |  | ? "$object is now a send target" | 
| 586 |  |  |  |  |  |  | : "send target is turned off."); | 
| 587 |  |  |  |  |  |  | } | 
| 588 |  |  |  |  |  |  | } | 
| 589 |  |  |  |  |  |  |  | 
| 590 |  |  |  |  |  |  | { | 
| 591 |  |  |  |  |  |  | my %object_to_text = ( | 
| 592 |  |  |  |  |  |  | soundcard 		=> 'soundcard channel', | 
| 593 |  |  |  |  |  |  | jack_client 	=> 'JACK client', | 
| 594 |  |  |  |  |  |  | jack_manual     => 'JACK manual port', | 
| 595 |  |  |  |  |  |  | jack_port   	=> 'JACK manual port', | 
| 596 |  |  |  |  |  |  | loop 			=> 'loop device', | 
| 597 |  |  |  |  |  |  | jack_ports_list => "JACK ports list", | 
| 598 |  |  |  |  |  |  | bus				=> "bus", | 
| 599 |  |  |  |  |  |  | ); | 
| 600 |  |  |  |  |  |  | sub object_as_text { | 
| 601 | 0 |  |  | 0 | 0 |  | my ($track, $direction) = @_; # $direction: source | send | 
| 602 | 0 |  |  |  |  |  | my $type_field = $direction."_type"; | 
| 603 | 0 |  |  |  |  |  | my $id_field   = $direction."_id"; | 
| 604 | 0 |  |  |  |  |  | my $text = $object_to_text{$track->$type_field}; | 
| 605 | 0 |  |  |  |  |  | $text .= ' '; | 
| 606 | 0 |  |  |  |  |  | $text .= $track->$id_field | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | sub input_object_text { # for text display | 
| 611 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 612 | 0 |  |  |  |  |  | $track->object_as_text('source'); | 
| 613 |  |  |  |  |  |  | } | 
| 614 |  |  |  |  |  |  |  | 
| 615 |  |  |  |  |  |  | sub output_object_text {   # text for user display | 
| 616 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 617 | 0 |  |  |  |  |  | $track->object_as_text('send'); | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | } | 
| 620 |  |  |  |  |  |  | sub bus_name { | 
| 621 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 622 | 0 | 0 |  |  |  |  | return unless $track->is_mix_track; | 
| 623 | 0 | 0 |  |  |  |  | $track->name eq 'Master' | 
| 624 |  |  |  |  |  |  | ? 'Main' | 
| 625 |  |  |  |  |  |  | : $track->name | 
| 626 |  |  |  |  |  |  | } | 
| 627 |  |  |  |  |  |  | sub source_status { | 
| 628 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 629 | 0 | 0 |  |  |  |  | return $track->current_wav if $track->rec_status eq PLAY ; | 
| 630 |  |  |  |  |  |  | #return $track->name eq 'Master' ? $track->bus_name : '' if $track->is_mix_track; | 
| 631 | 0 | 0 |  |  |  |  | return $track->bus_name . " bus" if $track->is_mix_track; | 
| 632 | 0 | 0 |  |  |  |  | return $track->source_id unless $track->source_type eq 'soundcard'; | 
| 633 | 0 |  |  |  |  |  | my $ch = $track->source_id; | 
| 634 | 0 |  |  |  |  |  | my @channels; | 
| 635 | 0 |  |  |  |  |  | push @channels, $_ for $ch .. ($ch + $track->width - 1); | 
| 636 | 0 |  |  |  |  |  | join '/', @channels | 
| 637 |  |  |  |  |  |  | } | 
| 638 |  |  |  |  |  |  | sub destination { | 
| 639 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 640 |  |  |  |  |  |  | # display logic | 
| 641 |  |  |  |  |  |  | # always show the bus | 
| 642 |  |  |  |  |  |  | # except for tracks that belongs to the bus null. | 
| 643 |  |  |  |  |  |  | # in that case, show the specific source. | 
| 644 |  |  |  |  |  |  | # | 
| 645 |  |  |  |  |  |  | # for these mix tracks, we use the | 
| 646 |  |  |  |  |  |  | # track's own send_type/send_id | 
| 647 |  |  |  |  |  |  |  | 
| 648 | 0 |  |  |  |  |  | my $out; | 
| 649 | 0 | 0 |  |  |  |  | $out .= $track->group unless $track->group =~ /^(null|Master)$/; | 
| 650 | 0 |  |  |  |  |  | my $send_id = $track->send_id; | 
| 651 | 0 |  |  |  |  |  | my $send_type = $track->send_type; | 
| 652 | 0 | 0 |  |  |  |  | return $out if ! $send_type; | 
| 653 | 0 | 0 |  |  |  |  | $out .=	', ' if $out; | 
| 654 | 0 |  |  |  |  |  | $out .= dest_string($send_type, $send_id, $track->width); | 
| 655 | 0 |  |  |  |  |  | $out | 
| 656 |  |  |  |  |  |  | } | 
| 657 |  |  |  |  |  |  | sub set_rec { | 
| 658 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 659 | 0 | 0 |  |  |  |  | if (my $t = $track->target){ | 
| 660 | 0 |  |  |  |  |  | my  $msg  = $track->name; | 
| 661 | 0 |  |  |  |  |  | $msg .= qq( is an alias to track "$t"); | 
| 662 | 0 | 0 |  |  |  |  | $msg .=  q( in project ") . $track->project . q(") | 
| 663 |  |  |  |  |  |  | if $track->project; | 
| 664 | 0 |  |  |  |  |  | $msg .= qq(.\n); | 
| 665 | 0 |  |  |  |  |  | $msg .= "Can't set a track alias to REC.\n"; | 
| 666 | 0 |  |  |  |  |  | Audio::Nama::throw($msg); | 
| 667 | 0 |  |  |  |  |  | return; | 
| 668 |  |  |  |  |  |  | } | 
| 669 | 0 |  |  |  |  |  | $track->set_rw(REC); | 
| 670 |  |  |  |  |  |  | } | 
| 671 |  |  |  |  |  |  | sub set_play { | 
| 672 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 673 | 0 |  |  |  |  |  | $track->set_rw(PLAY); | 
| 674 |  |  |  |  |  |  | } | 
| 675 |  |  |  |  |  |  | sub set_mon { | 
| 676 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 677 | 0 |  |  |  |  |  | $track->set_rw(MON); | 
| 678 |  |  |  |  |  |  | } | 
| 679 |  |  |  |  |  |  | sub set_off { | 
| 680 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 681 | 0 |  |  |  |  |  | $track->set_rw(OFF); | 
| 682 |  |  |  |  |  |  | } | 
| 683 |  |  |  |  |  |  |  | 
| 684 |  |  |  |  |  |  | sub set_rw { | 
| 685 | 0 |  |  | 0 | 0 |  | my ($track, $setting) = @_; | 
| 686 |  |  |  |  |  |  | #my $already = $track->rw eq $setting ? " already" : ""; | 
| 687 | 0 |  |  |  |  |  | $track->set(rw => $setting); | 
| 688 | 0 |  |  |  |  |  | my $status = $track->rec_status(); | 
| 689 | 0 | 0 |  |  |  |  | Audio::Nama::pager($track->name, " set to $setting", | 
| 690 |  |  |  |  |  |  | ($status ne $setting ? ", but current status is $status" : "")); | 
| 691 |  |  |  |  |  |  |  | 
| 692 |  |  |  |  |  |  | } | 
| 693 |  |  |  |  |  |  |  | 
| 694 |  |  |  |  |  |  |  | 
| 695 |  |  |  |  |  |  | # Operations performed by track objects | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | sub normalize { | 
| 698 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 699 | 0 | 0 |  |  |  |  | if ($track->rec_status ne PLAY){ | 
| 700 | 0 |  |  |  |  |  | Audio::Nama::throw($track->name, ": You must set track to PLAY before normalizing, skipping.\n"); | 
| 701 | 0 |  |  |  |  |  | return; | 
| 702 |  |  |  |  |  |  | } | 
| 703 |  |  |  |  |  |  | # track version will exist if PLAY status | 
| 704 | 0 |  |  |  |  |  | my $cmd = 'ecanormalize '; | 
| 705 | 0 |  |  |  |  |  | $cmd .= $track->full_path; | 
| 706 | 0 |  |  |  |  |  | Audio::Nama::pager("executing: $cmd\n"); | 
| 707 | 0 |  |  |  |  |  | system $cmd; | 
| 708 |  |  |  |  |  |  | } | 
| 709 |  |  |  |  |  |  | sub fixdc { | 
| 710 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 711 | 0 | 0 |  |  |  |  | if ($track->rec_status ne PLAY){ | 
| 712 | 0 |  |  |  |  |  | Audio::Nama::throw($track->name, ": You must set track to PLAY before fixing dc level, skipping.\n"); | 
| 713 | 0 |  |  |  |  |  | return; | 
| 714 |  |  |  |  |  |  | } | 
| 715 |  |  |  |  |  |  |  | 
| 716 | 0 |  |  |  |  |  | my $cmd = 'ecafixdc '; | 
| 717 | 0 |  |  |  |  |  | $cmd .= $track->full_path; | 
| 718 | 0 |  |  |  |  |  | Audio::Nama::pager("executing: $cmd\n"); | 
| 719 | 0 |  |  |  |  |  | system $cmd; | 
| 720 |  |  |  |  |  |  | } | 
| 721 |  |  |  |  |  |  | sub wav_length { | 
| 722 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 723 | 0 |  |  |  |  |  | Audio::Nama::wav_length($track->full_path) | 
| 724 |  |  |  |  |  |  | } | 
| 725 |  |  |  |  |  |  | sub wav_format{ | 
| 726 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 727 | 0 |  |  |  |  |  | Audio::Nama::wav_format($track->full_path) | 
| 728 |  |  |  |  |  |  | } | 
| 729 |  |  |  |  |  |  |  | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | sub mute { | 
| 732 |  |  |  |  |  |  |  | 
| 733 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 734 | 0 |  |  |  |  |  | my $nofade = shift; | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | # do nothing if track is already muted | 
| 737 | 0 | 0 |  |  |  |  | return if defined $track->old_vol_level(); | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | # do nothing if track has no volume operator | 
| 740 | 0 |  |  |  |  |  | my $vol = $track->vol_o; | 
| 741 | 0 | 0 |  |  |  |  | return unless $vol; | 
| 742 |  |  |  |  |  |  |  | 
| 743 |  |  |  |  |  |  | # store vol level for unmute | 
| 744 | 0 |  |  |  |  |  | $track->set(old_vol_level => $vol->params->[0]); | 
| 745 |  |  |  |  |  |  |  | 
| 746 | 0 | 0 |  |  |  |  | $nofade | 
| 747 |  |  |  |  |  |  | ? $vol->_modify_effect(1, $vol->mute_level) | 
| 748 |  |  |  |  |  |  | : $vol->fadeout | 
| 749 |  |  |  |  |  |  | } | 
| 750 |  |  |  |  |  |  | sub unmute { | 
| 751 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 752 | 0 |  |  |  |  |  | my $nofade = shift; | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | # do nothing if we are not muted | 
| 755 | 0 | 0 |  |  |  |  | return if ! defined $track->old_vol_level; | 
| 756 |  |  |  |  |  |  |  | 
| 757 | 0 | 0 |  |  |  |  | $nofade | 
| 758 |  |  |  |  |  |  | ? $track->vol_o->_modify_effect(1, $track->old_vol_level) | 
| 759 |  |  |  |  |  |  | : $track->vol_o->fadein($track->old_vol_level); | 
| 760 |  |  |  |  |  |  |  | 
| 761 | 0 |  |  |  |  |  | $track->set(old_vol_level => undef); | 
| 762 |  |  |  |  |  |  | } | 
| 763 |  |  |  |  |  |  | sub apply_ops { | 
| 764 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 765 | 0 |  |  |  |  |  | map{ $_->apply_op }	# add operator to the ecasound chain | 
| 766 | 0 |  |  |  |  |  | map{ fxn($_) } 		# convert to objects | 
| 767 | 0 |  |  |  |  |  | @{ $track->ops }  	# start with track ops list | 
|  | 0 |  |  |  |  |  |  | 
| 768 |  |  |  |  |  |  | } | 
| 769 |  |  |  |  |  |  | sub import_audio  { | 
| 770 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 771 | 0 | 0 |  |  |  |  | Audio::Nama::throw($track->name.": Cannot import audio to system track"), | 
| 772 |  |  |  |  |  |  | return if ! $track->is_user_track; | 
| 773 | 0 |  |  |  |  |  | my ($path, $frequency) = @_; | 
| 774 | 0 |  |  |  |  |  | $path = Audio::Nama::expand_tilde($path); | 
| 775 | 0 |  |  |  |  |  | my $version  = $track->last + 1; | 
| 776 | 0 | 0 |  |  |  |  | if ( ! -r $path ){ | 
| 777 | 0 |  |  |  |  |  | Audio::Nama::throw("$path: non-existent or unreadable file. No action.\n"); | 
| 778 | 0 |  |  |  |  |  | return; | 
| 779 |  |  |  |  |  |  | } | 
| 780 | 0 |  |  |  |  |  | my ($depth,$width,$freq) = split ',', Audio::Nama::wav_format($path); | 
| 781 | 0 |  |  |  |  |  | Audio::Nama::pager_newline("format: ", Audio::Nama::wav_format($path)); | 
| 782 | 0 |  | 0 |  |  |  | $frequency ||= $freq; | 
| 783 | 0 | 0 |  |  |  |  | if ( ! $frequency ){ | 
| 784 | 0 |  |  |  |  |  | Audio::Nama::throw("Cannot detect sample rate of $path. Skipping.", | 
| 785 |  |  |  |  |  |  | "Maybe 'import_audio  ' will help."); | 
| 786 |  |  |  |  |  |  | return | 
| 787 | 0 |  |  |  |  |  | } | 
| 788 | 0 |  |  |  |  |  | my $desired_frequency = freq( $config->{raw_to_disk_format} ); | 
| 789 | 0 |  |  |  |  |  | my $destination = join_path(Audio::Nama::this_wav_dir(),$track->name."_$version.wav"); | 
| 790 | 0 | 0 | 0 |  |  |  | if ( $frequency == $desired_frequency and $path =~ /.wav$/i){ | 
| 791 | 0 |  |  |  |  |  | Audio::Nama::pager_newline("copying $path to $destination"); | 
| 792 | 0 | 0 |  |  |  |  | copy($path, $destination) or die "copy failed: $!"; | 
| 793 |  |  |  |  |  |  | } else { | 
| 794 | 0 |  |  |  |  |  | my $format = Audio::Nama::signal_format($config->{raw_to_disk_format}, $width); | 
| 795 | 0 |  |  |  |  |  | Audio::Nama::pager_newline("importing $path as $destination, converting to $format"); | 
| 796 | 0 |  |  |  |  |  | Audio::Nama::teardown_engine(); | 
| 797 | 0 |  |  |  |  |  | my $ecs = qq(-f:$format -i:resample-hq,$frequency,"$path" -o:$destination); | 
| 798 | 0 |  |  |  |  |  | my $path = join_path(Audio::Nama::project_dir()."convert.ecs"); | 
| 799 | 0 |  |  |  |  |  | write_file($path, $ecs); | 
| 800 | 0 | 0 |  |  |  |  | Audio::Nama::load_ecs($path) or Audio::Nama::throw("$path: load failed, aborting"), return; | 
| 801 | 0 |  |  |  |  |  | Audio::Nama::eval_iam('start'); | 
| 802 | 0 |  |  |  |  |  | Audio::Nama::sleeper(0.2); sleep 1 while Audio::Nama::engine_running(); | 
|  | 0 |  |  |  |  |  |  | 
| 803 |  |  |  |  |  |  | } | 
| 804 | 0 | 0 |  |  |  |  | Audio::Nama::restart_wav_memoize() if $config->{opts}->{R}; # usually handled by reconfigure_engine() | 
| 805 |  |  |  |  |  |  | } | 
| 806 |  |  |  |  |  |  |  | 
| 807 | 0 | 0 |  | 0 | 0 |  | sub port_name { $_[0]->target || $_[0]->name } | 
| 808 |  |  |  |  |  |  | sub jack_manual_port { | 
| 809 | 0 |  |  | 0 | 0 |  | my ($track, $direction) = @_; | 
| 810 | 0 | 0 |  |  |  |  | $track->port_name . ($direction =~ /source|input/ ? '_in' : '_out'); | 
| 811 |  |  |  |  |  |  | } | 
| 812 |  |  |  |  |  |  |  | 
| 813 |  |  |  |  |  |  | sub bus_tree { # for solo function to work in sub buses | 
| 814 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 815 | 0 |  |  |  |  |  | my $mix = $track->group; | 
| 816 | 0 | 0 |  |  |  |  | return if $mix eq 'Main'; | 
| 817 | 0 |  |  |  |  |  | ($mix, $tn{$mix}->bus_tree); | 
| 818 |  |  |  |  |  |  | } | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | sub version_has_edits { | 
| 821 | 0 |  |  | 0 | 0 |  | my ($track) = @_; | 
| 822 |  |  |  |  |  |  | grep | 
| 823 | 0 | 0 |  |  |  |  | { 		$_->host_track eq $track->name | 
|  | 0 |  |  |  |  |  |  | 
| 824 |  |  |  |  |  |  | and $_->host_version == $track->monitor_version | 
| 825 |  |  |  |  |  |  | } values %Audio::Nama::Edit::by_name; | 
| 826 |  |  |  |  |  |  | } | 
| 827 |  |  |  |  |  |  | # current operator and current parameter for the track | 
| 828 | 0 |  | 0 | 0 | 0 |  | sub op { $project->{current_op}->{$_[0]->name} //= $_[0]->{ops}->[-1] } | 
| 829 |  |  |  |  |  |  |  | 
| 830 | 0 |  | 0 | 0 | 0 |  | sub param { $project->{current_param}->{$_[0]->op} //= 1 } | 
| 831 |  |  |  |  |  |  |  | 
| 832 |  |  |  |  |  |  | sub stepsize { | 
| 833 | 0 |  | 0 | 0 | 0 |  | $project->{current_stepsize}->{$_[0]->op}->[$_[0]->param] //= 0.01 | 
| 834 |  |  |  |  |  |  | # TODO use hint if available | 
| 835 |  |  |  |  |  |  | } | 
| 836 |  |  |  |  |  |  | sub pos { | 
| 837 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 838 | 0 |  |  | 0 |  |  | first_index{$_ eq $track->op} @{$track->ops}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 839 |  |  |  |  |  |  | } | 
| 840 |  |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  | sub set_track_class { | 
| 842 | 0 |  |  | 0 | 0 |  | my ($track, $class) = @_; | 
| 843 | 0 |  |  |  |  |  | bless $track, $class; | 
| 844 | 0 |  |  |  |  |  | $track->set(class => $class); | 
| 845 |  |  |  |  |  |  | } | 
| 846 |  |  |  |  |  |  | sub busify { | 
| 847 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 848 | 0 | 0 |  |  |  |  | Audio::Nama::add_bus($track->name) unless $track->is_system_track; | 
| 849 |  |  |  |  |  |  | } | 
| 850 |  |  |  |  |  |  | sub unbusify { | 
| 851 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 852 | 0 | 0 |  |  |  |  | return if $track->is_system_track; | 
| 853 | 0 |  |  |  |  |  | $track->set( rw => PLAY); | 
| 854 |  |  |  |  |  |  | } | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | sub shifted_length { | 
| 857 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 858 | 0 |  |  |  |  |  | my $setup_length; | 
| 859 | 0 | 0 |  |  |  |  | if ($track->region_start){ | 
| 860 | 0 |  |  |  |  |  | $setup_length = 	$track->shifted_region_end_time | 
| 861 |  |  |  |  |  |  | - $track->shifted_region_start_time | 
| 862 |  |  |  |  |  |  | } else { | 
| 863 | 0 |  |  |  |  |  | $setup_length = 	$track->wav_length; | 
| 864 |  |  |  |  |  |  | } | 
| 865 | 0 |  |  |  |  |  | $setup_length += $track->shifted_playat_time; | 
| 866 |  |  |  |  |  |  | } | 
| 867 |  |  |  |  |  |  |  | 
| 868 |  |  |  |  |  |  | sub version_comment { | 
| 869 | 0 |  |  | 0 | 0 |  | my ($track, $v) = @_; | 
| 870 | 0 | 0 |  |  |  |  | return unless $project->{track_version_comments}->{$track->name}{$v}; | 
| 871 | 0 |  |  |  |  |  | my $text   = $project->{track_version_comments}->{$track->name}{$v}{user}; | 
| 872 | 0 | 0 |  |  |  |  | $text .= " " if $text; | 
| 873 | 0 |  |  |  |  |  | my $system = $project->{track_version_comments}->{$track->name}{$v}{system}; | 
| 874 | 0 | 0 |  |  |  |  | $text .= "* $system" if $system; | 
| 875 | 0 | 0 |  |  |  |  | "$v: $text\n" if $text; | 
| 876 |  |  |  |  |  |  | } | 
| 877 |  |  |  |  |  |  | # Modified from Object.p to save class | 
| 878 |  |  |  |  |  |  | # should this be used in other classes? | 
| 879 |  |  |  |  |  |  | sub as_hash { | 
| 880 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 881 | 0 |  |  |  |  |  | my $class = ref $self; | 
| 882 | 0 |  |  |  |  |  | bless $self, 'HASH'; # easy magic | 
| 883 | 0 |  |  |  |  |  | my %guts = %{ $self }; | 
|  | 0 |  |  |  |  |  |  | 
| 884 | 0 |  |  |  |  |  | $guts{class} = $class; # make sure we save the correct class name | 
| 885 | 0 |  |  |  |  |  | bless $self, $class; # restore | 
| 886 | 0 |  |  |  |  |  | return \%guts; | 
| 887 |  |  |  |  |  |  | } | 
| 888 |  |  |  |  |  |  | sub latency_offset { | 
| 889 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 890 | 1 |  |  | 1 |  | 7 | no warnings 'uninitialized'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 1419 |  | 
| 891 |  |  |  |  |  |  | $setup->{latency}->{sibling}->{$track->name} | 
| 892 | 0 |  |  |  |  |  | - $setup->{latency}->{track}->{$track->name}->{total}; | 
| 893 |  |  |  |  |  |  | } | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  |  | 
| 896 |  |  |  |  |  |  | sub input_object { | 
| 897 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 898 |  |  |  |  |  |  | $Audio::Nama::IO::by_name{$track->name}->{input} | 
| 899 | 0 |  |  |  |  |  | } | 
| 900 |  |  |  |  |  |  | sub output_object { | 
| 901 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 902 |  |  |  |  |  |  | $Audio::Nama::IO::by_name{$track->name}->{output} | 
| 903 | 0 |  |  |  |  |  | } | 
| 904 |  |  |  |  |  |  | sub capture_latency { | 
| 905 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 906 | 0 |  |  |  |  |  | my $io = $track->input_object; | 
| 907 | 0 | 0 |  |  |  |  | return $io->capture_latency if ref $io; | 
| 908 |  |  |  |  |  |  | } | 
| 909 |  |  |  |  |  |  | sub playback_latency { | 
| 910 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 911 | 0 |  |  |  |  |  | my $io = $track->input_object; | 
| 912 | 0 | 0 |  |  |  |  | return $io->playback_latency if ref $io; | 
| 913 |  |  |  |  |  |  | } | 
| 914 |  |  |  |  |  |  | sub sibling_latency { | 
| 915 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 916 | 0 |  |  |  |  |  | $setup->{latency}->{sibling}->{$track->name} | 
| 917 |  |  |  |  |  |  | } | 
| 918 |  |  |  |  |  |  | sub sibling_count { | 
| 919 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 920 | 0 |  |  |  |  |  | $setup->{latency}->{sibling_count}->{$track->name} | 
| 921 |  |  |  |  |  |  | } | 
| 922 |  |  |  |  |  |  |  | 
| 923 |  |  |  |  |  |  | sub set_comment { | 
| 924 | 0 |  |  | 0 | 0 |  | my ($track, $comment) = @_; | 
| 925 | 0 |  |  |  |  |  | $project->{track_comments}->{$track->name} = $comment | 
| 926 |  |  |  |  |  |  | } | 
| 927 | 0 |  |  | 0 | 0 |  | sub comment { $project->{track_comments}->{$_[0]->name} } | 
| 928 |  |  |  |  |  |  |  | 
| 929 |  |  |  |  |  |  | sub show_version_comments { | 
| 930 | 0 |  |  | 0 | 0 |  | my ($t, @v) = @_; | 
| 931 | 0 | 0 |  |  |  |  | return unless @v; | 
| 932 | 0 |  |  |  |  |  | Audio::Nama::pager(map{ $t->version_comment($_) } @v); | 
|  | 0 |  |  |  |  |  |  | 
| 933 |  |  |  |  |  |  | } | 
| 934 |  |  |  |  |  |  | sub add_version_comment { | 
| 935 | 0 |  |  | 0 | 0 |  | my ($t,$v,$text) = @_; | 
| 936 | 0 | 0 |  |  |  |  | $t->targets->{$v} or Audio::Nama::throw("$v: no such version"), return; | 
| 937 | 0 |  |  |  |  |  | $project->{track_version_comments}->{$t->name}{$v}{user} = $text; | 
| 938 | 0 |  |  |  |  |  | $t->version_comment($v); | 
| 939 |  |  |  |  |  |  | } | 
| 940 |  |  |  |  |  |  | sub add_system_version_comment { | 
| 941 | 0 |  |  | 0 | 0 |  | my ($t,$v,$text) = @_; | 
| 942 | 0 | 0 |  |  |  |  | $t->targets->{$v} or Audio::Nama::throw("$v: no such version"), return; | 
| 943 | 0 |  |  |  |  |  | $project->{track_version_comments}{$t->name}{$v}{system} = $text; | 
| 944 | 0 |  |  |  |  |  | $t->version_comment($v); | 
| 945 |  |  |  |  |  |  | } | 
| 946 |  |  |  |  |  |  | sub remove_version_comment { | 
| 947 | 0 |  |  | 0 | 0 |  | my ($t,$v) = @_; | 
| 948 | 0 | 0 |  |  |  |  | $t->targets->{$v} or Audio::Nama::throw("$v: no such version"), return; | 
| 949 | 0 |  |  |  |  |  | delete $project->{track_version_comments}{$t->name}{$v}{user}; | 
| 950 | 0 | 0 |  |  |  |  | $t->version_comment($v) || "$v: [comment deleted]\n"; | 
| 951 |  |  |  |  |  |  | } | 
| 952 |  |  |  |  |  |  | sub remove_system_version_comment { | 
| 953 | 0 |  |  | 0 | 0 |  | my ($t,$v) = @_; | 
| 954 |  |  |  |  |  |  | delete $project->{track_version_comments}{$t->name}{$v}{system} if | 
| 955 | 0 | 0 |  |  |  |  | $project->{track_version_comments}{$t->name}{$v} | 
| 956 |  |  |  |  |  |  | } | 
| 957 |  |  |  |  |  |  | sub rec_setup_script { | 
| 958 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 959 | 0 |  |  |  |  |  | join_path(Audio::Nama::project_dir(), $track->name."-rec-setup.sh") | 
| 960 |  |  |  |  |  |  | } | 
| 961 |  |  |  |  |  |  | sub rec_cleanup_script { | 
| 962 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 963 | 0 |  |  |  |  |  | join_path(Audio::Nama::project_dir(), $track->name."-rec-cleanup.sh") | 
| 964 |  |  |  |  |  |  | } | 
| 965 | 0 |  |  | 0 | 0 |  | sub is_region { defined $_[0]->{region_start} } | 
| 966 |  |  |  |  |  |  |  | 
| 967 | 0 |  | 0 | 0 | 0 |  | sub current_edit { $_[0]->{current_edit}//={} } | 
| 968 |  |  |  |  |  |  |  | 
| 969 |  |  |  |  |  |  | sub first_effect_of_type { | 
| 970 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 971 | 0 |  |  |  |  |  | my $type = shift; | 
| 972 | 0 |  |  |  |  |  | for my $op ( @{$track->ops} ){ | 
|  | 0 |  |  |  |  |  |  | 
| 973 | 0 |  |  |  |  |  | my $FX = Audio::Nama::fxn($op); | 
| 974 | 0 | 0 |  |  |  |  | return $FX if $FX->type =~ /$type/ # Plate matches el:Plate | 
| 975 |  |  |  |  |  |  | } | 
| 976 |  |  |  |  |  |  | } | 
| 977 |  |  |  |  |  |  | sub is_mix_track { | 
| 978 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 979 | 0 | 0 | 0 |  |  |  | ($bn{$track->name} or $track->name eq 'Master') and $track->rw eq MON | 
| 980 |  |  |  |  |  |  | } | 
| 981 | 0 |  |  | 0 | 0 |  | sub bus { $bn{$_[0]->group} } | 
| 982 |  |  |  |  |  |  |  | 
| 983 |  |  |  |  |  |  | sub effect_id_by_name { | 
| 984 | 0 |  |  | 0 | 0 |  | my $track = shift; | 
| 985 | 0 |  |  |  |  |  | my $ident = shift; | 
| 986 | 0 |  |  |  |  |  | for my $FX ($track->fancy_ops_o) | 
| 987 | 0 | 0 |  |  |  |  | { return $FX->id if $FX->name eq $ident } | 
| 988 |  |  |  |  |  |  | } | 
| 989 |  |  |  |  |  |  | sub effect_nickname_count { | 
| 990 | 0 |  |  | 0 | 0 |  | my ($track, $nick) = @_; | 
| 991 | 0 |  |  |  |  |  | my $count = 0; | 
| 992 | 0 | 0 |  |  |  |  | for my $FX ($track->fancy_ops_o){ $count++ if $FX->name =~ /^$nick\d*$/ } | 
|  | 0 |  |  |  |  |  |  | 
| 993 |  |  |  |  |  |  | $count | 
| 994 | 0 |  |  |  |  |  | } | 
| 995 |  |  |  |  |  |  | sub unique_surname { | 
| 996 | 0 |  |  | 0 | 0 |  | my ($track, $surname) = @_; | 
| 997 |  |  |  |  |  |  | # increment supplied surname to be unique to the track if necessary | 
| 998 |  |  |  |  |  |  | # return arguments: | 
| 999 |  |  |  |  |  |  | # $surname, $previous_surnames | 
| 1000 | 0 |  |  |  |  |  | my $max = undef; | 
| 1001 | 0 |  |  |  |  |  | my %found; | 
| 1002 | 0 |  |  |  |  |  | for my $FX ($track->fancy_ops_o) | 
| 1003 |  |  |  |  |  |  | { | 
| 1004 | 0 | 0 |  |  |  |  | if( $FX->surname =~ /^$surname(\d*)$/) | 
| 1005 |  |  |  |  |  |  | { | 
| 1006 | 0 |  |  |  |  |  | $found{$FX->surname}++; | 
| 1007 | 1 |  |  | 1 |  | 5 | no warnings qw(uninitialized numeric); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 776 |  | 
| 1008 | 0 | 0 |  |  |  |  | $max = $1 if $1 > $max; | 
| 1009 |  |  |  |  |  |  | } | 
| 1010 |  |  |  |  |  |  | } | 
| 1011 | 0 | 0 |  |  |  |  | if (%found){ $surname.++$max, join ' ',sort keys %found } else { $surname } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1012 |  |  |  |  |  |  | } | 
| 1013 |  |  |  |  |  |  | sub unique_nickname { | 
| 1014 | 0 |  |  | 0 | 0 |  | my ($track, $nickname) = @_; | 
| 1015 | 0 |  |  |  |  |  | my $i = 0; | 
| 1016 | 0 |  |  |  |  |  | my @found; | 
| 1017 | 0 |  |  |  |  |  | for my $FX ($track->fancy_ops_o) | 
| 1018 |  |  |  |  |  |  | { | 
| 1019 | 0 | 0 |  |  |  |  | if( $FX->name =~ /^$nickname(\d*)$/) | 
| 1020 |  |  |  |  |  |  | { | 
| 1021 | 0 |  |  |  |  |  | push @found, $FX->name; | 
| 1022 | 0 | 0 | 0 |  |  |  | $i = $1 if $1 and $1 > $i | 
| 1023 |  |  |  |  |  |  | } | 
| 1024 |  |  |  |  |  |  | } | 
| 1025 | 0 | 0 |  |  |  |  | $nickname. (@found ? ++$i : ""), "@found" | 
| 1026 |  |  |  |  |  |  | } | 
| 1027 |  |  |  |  |  |  | # return effect IDs matching a surname | 
| 1028 |  |  |  |  |  |  | sub with_surname { | 
| 1029 | 0 |  |  | 0 | 0 |  | my ($track, $surname) = @_; | 
| 1030 | 0 |  |  |  |  |  | my @found; | 
| 1031 | 0 |  |  |  |  |  | for my $FX ($track->fancy_ops_o) | 
| 1032 | 0 | 0 |  |  |  |  | { push @found, $FX->id if $FX->surname eq $surname } | 
| 1033 | 0 | 0 |  |  |  |  | @found ? "@found" : undef | 
| 1034 |  |  |  |  |  |  | } | 
| 1035 | 0 |  |  | 0 | 0 |  | sub vol_level { my $self = shift; try { $self->vol_o->params->[0] } } | 
|  | 0 |  |  | 0 |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1036 | 0 |  |  | 0 | 0 |  | sub pan_level { my $self = shift; try { $self->pan_o->params->[0] } } | 
|  | 0 |  |  | 0 |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1037 | 0 |  |  | 0 | 0 |  | sub vol_o { my $self = shift; fxn($self->vol) } | 
|  | 0 |  |  |  |  |  |  | 
| 1038 | 0 |  |  | 0 | 0 |  | sub pan_o { my $self = shift; fxn($self->pan) } | 
|  | 0 |  |  |  |  |  |  | 
| 1039 |  |  |  |  |  |  | { my %system_track = map{ $_, 1} qw( Master Mixdown Eq Low | 
| 1040 |  |  |  |  |  |  | Mid High Boost ); | 
| 1041 | 0 |  |  | 0 | 0 |  | sub is_user_track { ! $system_track{$_[0]->name} } | 
| 1042 | 0 |  |  | 0 | 0 |  | sub is_system_track { $system_track{$_[0]->name} } | 
| 1043 |  |  |  |  |  |  | } | 
| 1044 |  |  |  |  |  |  | sub is_comment { | 
| 1045 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 1046 | 0 |  |  |  |  |  | $Audio::Nama::project->{track_comments}->{$self->name} | 
| 1047 |  |  |  |  |  |  | } | 
| 1048 |  |  |  |  |  |  | sub is_version_comment { | 
| 1049 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 1050 | 0 |  |  |  |  |  | my $version = shift; | 
| 1051 | 0 |  |  |  |  |  | my $comments = $project->{track_version_comments}->{$self->name}->{$version}; | 
| 1052 |  |  |  |  |  |  | $comments and $comments->{user} | 
| 1053 | 0 | 0 |  |  |  |  | } | 
| 1054 |  |  |  |  |  |  | } # end package | 
| 1055 |  |  |  |  |  |  |  | 
| 1056 |  |  |  |  |  |  |  | 
| 1057 |  |  |  |  |  |  | # subclasses | 
| 1058 |  |  |  |  |  |  |  | 
| 1059 |  |  |  |  |  |  |  | 
| 1060 |  |  |  |  |  |  | { | 
| 1061 |  |  |  |  |  |  | package Audio::Nama::SimpleTrack; # used for Master track | 
| 1062 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 553 |  | 
| 1063 | 1 |  |  | 1 |  | 7 | use Modern::Perl; use Carp; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 1 |  | 
|  | 1 |  |  | 1 |  | 8 |  | 
|  | 1 |  |  |  |  | 108 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 58 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 47 |  | 
| 1064 | 1 |  |  | 1 |  | 799 | use SUPER; | 
|  | 1 |  |  |  |  | 2813 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 1065 | 1 |  |  | 1 |  | 40 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 298 |  | 
| 1066 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::Track'; | 
| 1067 |  |  |  |  |  |  | sub rec_status { | 
| 1068 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1069 | 0 | 0 |  |  |  |  | $track->rw ne OFF ? MON : OFF | 
| 1070 |  |  |  |  |  |  | } | 
| 1071 |  |  |  |  |  |  | sub destination { | 
| 1072 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1073 | 0 | 0 |  |  |  |  | return 'Mixdown' if $tn{Mixdown}->rec_status eq REC; | 
| 1074 | 0 | 0 |  |  |  |  | return $track->SUPER() if $track->rec_status ne OFF | 
| 1075 |  |  |  |  |  |  | } | 
| 1076 |  |  |  |  |  |  | #sub rec_status_display { $_[0]->rw ne OFF ? PLAY : OFF } | 
| 1077 |  |  |  | 0 |  |  | sub busify {} | 
| 1078 |  |  |  | 0 |  |  | sub unbusify {} | 
| 1079 |  |  |  |  |  |  | } | 
| 1080 |  |  |  |  |  |  | { | 
| 1081 |  |  |  |  |  |  | package Audio::Nama::MasteringTrack; # used for mastering chains | 
| 1082 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 464 |  | 
| 1083 | 1 |  |  | 1 |  | 5 | use Modern::Perl; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 1 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 107 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 1084 | 1 |  |  | 1 |  | 5 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 182 |  | 
| 1085 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::SimpleTrack'; | 
| 1086 |  |  |  |  |  |  |  | 
| 1087 |  |  |  |  |  |  | sub rec_status{ | 
| 1088 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1089 | 0 | 0 |  |  |  |  | return OFF if $track->engine_group ne $this_engine->name; | 
| 1090 | 0 | 0 |  |  |  |  | $mode->{mastering} ? MON :  OFF; | 
| 1091 |  |  |  |  |  |  | } | 
| 1092 |  |  |  | 0 |  |  | sub source_status {} | 
| 1093 | 0 |  |  | 0 |  |  | sub group_last {0} | 
| 1094 | 0 |  |  | 0 |  |  | sub version {0} | 
| 1095 |  |  |  |  |  |  | } | 
| 1096 |  |  |  |  |  |  | { | 
| 1097 |  |  |  |  |  |  | package Audio::Nama::EarTrack; # for submix helper tracks | 
| 1098 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 444 |  | 
| 1099 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Util qw(dest_string); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 1100 | 1 |  |  | 1 |  | 9 | use Modern::Perl; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 1 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 93 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 1101 | 1 |  |  | 1 |  | 5 | use SUPER; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 1102 | 1 |  |  | 1 |  | 29 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 211 |  | 
| 1103 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::SlaveTrack'; | 
| 1104 |  |  |  |  |  |  | sub destination { | 
| 1105 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1106 | 0 |  |  |  |  |  | my $bus = $track->bus; | 
| 1107 | 0 |  |  |  |  |  | dest_string($bus->send_type,$bus->send_id, $track->width); | 
| 1108 |  |  |  |  |  |  | } | 
| 1109 | 0 |  |  | 0 |  |  | sub source_status { $_[0]->target } | 
| 1110 | 0 |  |  | 0 |  |  | sub rec_status { $_[0]->{rw} } | 
| 1111 | 0 |  |  | 0 |  |  | sub width { $_[0]->{width} } | 
| 1112 |  |  |  |  |  |  | } | 
| 1113 |  |  |  |  |  |  | { | 
| 1114 |  |  |  |  |  |  | package Audio::Nama::SlaveTrack; # for instrument monitor bus | 
| 1115 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 511 |  | 
| 1116 | 1 |  |  | 1 |  | 5 | use Modern::Perl; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 1 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 99 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 39 |  | 
| 1117 | 1 |  |  | 1 |  | 5 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 385 |  | 
| 1118 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::Track'; | 
| 1119 | 0 |  |  | 0 |  |  | sub width { $tn{$_[0]->target}->width } | 
| 1120 | 0 |  |  | 0 |  |  | sub rec_status { $tn{$_[0]->target}->rec_status } | 
| 1121 | 0 |  |  | 0 |  |  | sub full_path { $tn{$_[0]->target}->full_path} | 
| 1122 | 0 |  |  | 0 |  |  | sub monitor_version { $tn{$_[0]->target}->monitor_version} | 
| 1123 | 0 |  |  | 0 |  |  | sub source_type { $tn{$_[0]->target}->source_type} | 
| 1124 | 0 |  |  | 0 |  |  | sub source_id { $tn{$_[0]->target}->source_id} | 
| 1125 | 0 |  |  | 0 |  |  | sub source_status { $tn{$_[0]->target}->source_status } | 
| 1126 | 0 |  |  | 0 |  |  | sub send_type { $tn{$_[0]->target}->send_type} | 
| 1127 | 0 |  |  | 0 |  |  | sub send_id { $tn{$_[0]->target}->send_id} | 
| 1128 | 0 |  |  | 0 |  |  | sub dir { $tn{$_[0]->target}->dir } | 
| 1129 |  |  |  |  |  |  | } | 
| 1130 |  |  |  |  |  |  | { | 
| 1131 |  |  |  |  |  |  | package Audio::Nama::BoostTrack; | 
| 1132 |  |  |  |  |  |  | # | 
| 1133 |  |  |  |  |  |  | # this subclass, intended for the single track "Boost", | 
| 1134 |  |  |  |  |  |  | # disables routing of the mastering network | 
| 1135 |  |  |  |  |  |  | # when the mastering mode is disabled. | 
| 1136 |  |  |  |  |  |  |  | 
| 1137 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 462 |  | 
| 1138 | 1 |  |  | 1 |  | 5 | use Modern::Perl; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 2 |  | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 100 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 1139 | 1 |  |  | 1 |  | 5 | no warnings qw(uninitialized redefine); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 117 |  | 
| 1140 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::SlaveTrack'; | 
| 1141 |  |  |  |  |  |  | sub rec_status{ | 
| 1142 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1143 | 0 | 0 |  |  |  |  | $mode->{mastering} ? MON :  OFF; | 
| 1144 |  |  |  |  |  |  | } | 
| 1145 |  |  |  |  |  |  | } | 
| 1146 |  |  |  |  |  |  | { | 
| 1147 |  |  |  |  |  |  | package Audio::Nama::CacheRecTrack; # for graph generation | 
| 1148 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 471 |  | 
| 1149 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 185 |  | 
| 1150 |  |  |  |  |  |  | our @ISA = qw(Audio::Nama::SlaveTrack); | 
| 1151 |  |  |  |  |  |  | sub current_version { | 
| 1152 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1153 | 0 |  |  |  |  |  | my $target = $tn{$track->target}; | 
| 1154 | 0 |  |  |  |  |  | $target->last + 1 | 
| 1155 |  |  |  |  |  |  | # 	if ($target->rec_status eq PLAY | 
| 1156 |  |  |  |  |  |  | # 		or $target->rec_status eq REC and $bn{$track->target}){ | 
| 1157 |  |  |  |  |  |  | # 	} | 
| 1158 |  |  |  |  |  |  | } | 
| 1159 |  |  |  |  |  |  | sub current_wav { | 
| 1160 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1161 | 0 |  |  |  |  |  | $tn{$track->target}->name . '_' . $track->current_version . '.wav' | 
| 1162 |  |  |  |  |  |  | } | 
| 1163 | 0 |  |  | 0 |  |  | sub full_path { my $track = shift; Audio::Nama::join_path( $track->dir, $track->current_wav) } | 
|  | 0 |  |  |  |  |  |  | 
| 1164 |  |  |  |  |  |  | } | 
| 1165 |  |  |  |  |  |  | { | 
| 1166 |  |  |  |  |  |  | package Audio::Nama::MixDownTrack; | 
| 1167 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 9 |  | 
|  | 1 |  |  |  |  | 428 |  | 
| 1168 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 1169 | 1 |  |  | 1 |  | 5 | use SUPER; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 1170 |  |  |  |  |  |  | our @ISA = qw(Audio::Nama::Track); | 
| 1171 |  |  |  |  |  |  | sub current_version { | 
| 1172 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1173 | 0 |  |  |  |  |  | my $last = $track->last; | 
| 1174 | 0 |  |  |  |  |  | my $status = $track->rec_status; | 
| 1175 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', "last: $last status: $status"); | 
| 1176 | 0 | 0 |  |  |  |  | if 	($status eq REC){ return ++$last} | 
|  | 0 | 0 |  |  |  |  |  | 
| 1177 | 0 |  |  |  |  |  | elsif ( $status eq PLAY){ return $track->monitor_version } | 
| 1178 | 0 |  |  |  |  |  | else { return 0 } | 
| 1179 |  |  |  |  |  |  | } | 
| 1180 |  |  |  |  |  |  | sub source_status { | 
| 1181 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1182 | 0 | 0 |  |  |  |  | return 'Master' if $track->rec_status eq REC; | 
| 1183 | 0 |  |  |  |  |  | my $super = $track->super('source_status'); | 
| 1184 | 0 |  |  |  |  |  | $super->($track) | 
| 1185 |  |  |  |  |  |  | } | 
| 1186 |  |  |  |  |  |  | sub destination { | 
| 1187 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1188 | 0 | 0 |  |  |  |  | $tn{Master}->destination if $track->rec_status eq PLAY | 
| 1189 |  |  |  |  |  |  | } | 
| 1190 |  |  |  |  |  |  | sub rec_status { | 
| 1191 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1192 | 0 | 0 |  |  |  |  | return REC if $track->rw eq REC; | 
| 1193 | 0 |  |  |  |  |  | Audio::Nama::Track::rec_status($track); | 
| 1194 |  |  |  |  |  |  | } | 
| 1195 | 0 |  |  | 0 |  |  | sub forbid_user_ops { 1 } | 
| 1196 |  |  |  |  |  |  | } | 
| 1197 |  |  |  |  |  |  | { | 
| 1198 | 1 |  |  | 1 |  | 304 | package Audio::Nama::EditTrack; use Carp qw(carp cluck); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 53 |  | 
| 1199 | 1 |  |  | 1 |  | 4 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 427 |  | 
| 1200 | 1 |  |  | 1 |  | 4 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 408 |  | 
| 1201 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::Track'; | 
| 1202 |  |  |  |  |  |  | our $AUTOLOAD; | 
| 1203 |  |  |  |  |  |  | sub AUTOLOAD { | 
| 1204 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 1205 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', $self->name, ": args @_"); | 
| 1206 |  |  |  |  |  |  | # get tail of method call | 
| 1207 | 0 |  |  |  |  |  | my ($call) = $AUTOLOAD =~ /([^:]+)$/; | 
| 1208 | 0 |  |  |  |  |  | $Audio::Nama::Edit::by_name{$self->name}->$call(@_); | 
| 1209 |  |  |  |  |  |  | } | 
| 1210 |  |  |  | 0 |  |  | sub DESTROY {} | 
| 1211 |  |  |  |  |  |  | sub current_version { | 
| 1212 | 0 |  |  | 0 |  |  | my $track = shift; | 
| 1213 | 0 |  |  |  |  |  | my $last = $track->last; | 
| 1214 | 0 |  |  |  |  |  | my $status = $track->rec_status; | 
| 1215 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', "last: $last status: $status"); | 
| 1216 | 0 | 0 |  |  |  |  | if 	($status eq REC){ return ++$last} | 
|  | 0 | 0 |  |  |  |  |  | 
| 1217 | 0 |  |  |  |  |  | elsif ( $status eq PLAY){ return $track->monitor_version } | 
| 1218 | 0 |  |  |  |  |  | else { return 0 } | 
| 1219 |  |  |  |  |  |  | } | 
| 1220 |  |  |  |  |  |  | sub playat_time { | 
| 1221 | 0 |  |  | 0 |  |  | logpkg(__FILE__,__LINE__,'logcluck',$_[0]->name . "->playat_time"); | 
| 1222 | 0 |  |  |  |  |  | $_[0]->play_start_time | 
| 1223 |  |  |  |  |  |  | } | 
| 1224 |  |  |  |  |  |  | } | 
| 1225 |  |  |  |  |  |  | { | 
| 1226 |  |  |  |  |  |  | package Audio::Nama::VersionTrack; | 
| 1227 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 442 |  | 
| 1228 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 108 |  | 
| 1229 |  |  |  |  |  |  | our @ISA ='Audio::Nama::Track'; | 
| 1230 |  |  |  | 0 |  |  | sub set_version {} | 
| 1231 | 0 |  |  | 0 |  |  | sub versions { [$_[0]->version] } | 
| 1232 |  |  |  |  |  |  | } | 
| 1233 |  |  |  |  |  |  | { | 
| 1234 |  |  |  |  |  |  | package Audio::Nama::Clip; | 
| 1235 |  |  |  |  |  |  |  | 
| 1236 |  |  |  |  |  |  | # Clips are the units of audio used to | 
| 1237 |  |  |  |  |  |  | #  to make sequences. | 
| 1238 |  |  |  |  |  |  |  | 
| 1239 |  |  |  |  |  |  | # A clip is created from a track. Clips extend the Track | 
| 1240 |  |  |  |  |  |  | # class in providing a position which derives from the | 
| 1241 |  |  |  |  |  |  | # object's ordinal position in an array (clips attribute) of | 
| 1242 |  |  |  |  |  |  | # the parent sequence object. | 
| 1243 |  |  |  |  |  |  |  | 
| 1244 |  |  |  |  |  |  | # Clips differ from tracks in that clips | 
| 1245 |  |  |  |  |  |  | # their one-based position (index) in the sequence items array. | 
| 1246 |  |  |  |  |  |  | # index is one-based. | 
| 1247 |  |  |  |  |  |  |  | 
| 1248 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 432 |  | 
| 1249 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 371 |  | 
| 1250 |  |  |  |  |  |  | our @ISA = qw( Audio::Nama::VersionTrack Audio::Nama::Track ); | 
| 1251 |  |  |  |  |  |  |  | 
| 1252 | 0 |  |  | 0 |  |  | sub sequence { my $self = shift; $Audio::Nama::bn{$self->group} }; | 
|  | 0 |  |  |  |  |  |  | 
| 1253 |  |  |  |  |  |  |  | 
| 1254 | 0 |  |  | 0 |  |  | sub index { my $self = shift; my $i = 0; | 
|  | 0 |  |  |  |  |  |  | 
| 1255 | 0 |  |  |  |  |  | for( @{$self->sequence->items} ){ | 
|  | 0 |  |  |  |  |  |  | 
| 1256 | 0 |  |  |  |  |  | $i++; | 
| 1257 | 0 | 0 |  |  |  |  | return $i if $self->name eq $_ | 
| 1258 |  |  |  |  |  |  | } | 
| 1259 |  |  |  |  |  |  | } | 
| 1260 |  |  |  |  |  |  | sub predecessor { | 
| 1261 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 1262 | 0 |  |  |  |  |  | $self->sequence->clip($self->index - 1) | 
| 1263 |  |  |  |  |  |  | } | 
| 1264 |  |  |  |  |  |  | sub duration { | 
| 1265 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 1266 |  |  |  |  |  |  | $self->{duration} | 
| 1267 |  |  |  |  |  |  | ? Audio::Nama::Mark::duration_from_tag($self->{duration}) | 
| 1268 | 0 | 0 |  |  |  |  | : $self->is_region | 
|  |  | 0 |  |  |  |  |  | 
| 1269 |  |  |  |  |  |  | ? $self->region_end_time - $self->region_start_time | 
| 1270 |  |  |  |  |  |  | : $self->wav_length; | 
| 1271 |  |  |  |  |  |  | } | 
| 1272 |  |  |  |  |  |  | sub endpoint { | 
| 1273 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 1274 | 0 | 0 |  |  |  |  | $self->duration + ( $self->predecessor ?  $self->predecessor->endpoint : 0 ) | 
| 1275 |  |  |  |  |  |  | } | 
| 1276 |  |  |  |  |  |  | sub playat_time { | 
| 1277 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 1278 | 0 |  |  |  |  |  | my $previous = $self->predecessor; | 
| 1279 | 0 | 0 |  |  |  |  | $previous ? $previous->endpoint : 0 | 
| 1280 |  |  |  |  |  |  | } | 
| 1281 |  |  |  |  |  |  |  | 
| 1282 |  |  |  |  |  |  | # we currently are not compatible with offset run mode | 
| 1283 |  |  |  |  |  |  | # perhaps we can enforce OFF status for clips under | 
| 1284 |  |  |  |  |  |  | # offset run mode | 
| 1285 |  |  |  |  |  |  |  | 
| 1286 |  |  |  |  |  |  | } # end package | 
| 1287 |  |  |  |  |  |  | { | 
| 1288 |  |  |  |  |  |  | package Audio::Nama::Spacer; | 
| 1289 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::Clip'; | 
| 1290 | 1 |  |  | 1 |  | 6 | use SUPER; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 1291 | 1 |  |  | 1 |  | 35 | use Audio::Nama::Object qw(duration); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 11 |  | 
| 1292 | 0 |  |  | 0 |  |  | sub rec_status { OFF } | 
| 1293 |  |  |  |  |  |  | sub new { | 
| 1294 | 0 |  |  | 0 |  |  | my ($class,%args) = @_; | 
| 1295 |  |  |  |  |  |  |  | 
| 1296 |  |  |  |  |  |  | # remove args we will process | 
| 1297 | 0 |  |  |  |  |  | my $duration = delete $args{duration}; | 
| 1298 |  |  |  |  |  |  |  | 
| 1299 |  |  |  |  |  |  | # give the remainder to the superclass constructor | 
| 1300 | 0 |  |  |  |  |  | @_ = ($class, %args); | 
| 1301 | 0 |  |  |  |  |  | my $self = super(); | 
| 1302 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug',"new object: ", json_out($self->as_hash)); | 
| 1303 |  |  |  |  |  |  | #logpkg(__FILE__,__LINE__,'debug', "items: ",json_out($items)); | 
| 1304 |  |  |  |  |  |  |  | 
| 1305 |  |  |  |  |  |  | # set the args removed above | 
| 1306 | 0 |  |  |  |  |  | $self->{duration} = $duration; | 
| 1307 | 0 |  |  |  |  |  | $self; | 
| 1308 |  |  |  |  |  |  | } | 
| 1309 |  |  |  |  |  |  | } # end package | 
| 1310 |  |  |  |  |  |  | { | 
| 1311 |  |  |  |  |  |  | package Audio::Nama::WetTrack; # for inserts | 
| 1312 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 443 |  | 
| 1313 | 1 |  |  | 1 |  | 5 | use Modern::Perl; use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  | 1 |  | 2 |  | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 102 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 72 |  | 
| 1314 |  |  |  |  |  |  | our @ISA = 'Audio::Nama::SlaveTrack'; | 
| 1315 |  |  |  |  |  |  | } | 
| 1316 |  |  |  |  |  |  |  | 
| 1317 |  |  |  |  |  |  | # ----------- Track_subs ------------- | 
| 1318 |  |  |  |  |  |  | { | 
| 1319 |  |  |  |  |  |  | package Audio::Nama; | 
| 1320 | 1 |  |  | 1 |  | 5 | use Modern::Perl; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 1321 |  |  |  |  |  |  |  | 
| 1322 |  |  |  |  |  |  | # usual track | 
| 1323 |  |  |  |  |  |  |  | 
| 1324 |  |  |  |  |  |  | sub add_track { | 
| 1325 |  |  |  |  |  |  |  | 
| 1326 | 0 |  |  | 0 |  |  | logsub("&add_track"); | 
| 1327 |  |  |  |  |  |  | #return if transport_running(); | 
| 1328 | 0 |  |  |  |  |  | my ($name, @params) = @_; | 
| 1329 | 0 |  |  |  |  |  | my %vals = (name => $name, @params); | 
| 1330 | 0 |  | 0 |  |  |  | my $class = $vals{class} // 'Audio::Nama::Track'; | 
| 1331 | 1 |  |  | 1 |  | 153 | { no warnings 'uninitialized'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 1136 |  | 
|  | 0 |  |  |  |  |  |  | 
| 1332 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "name: $name, ch_r: $gui->{_chr}, ch_m: $gui->{_chm}"); | 
| 1333 |  |  |  |  |  |  | } | 
| 1334 |  |  |  |  |  |  | Audio::Nama::throw("$name: track name already in use. Skipping."), return | 
| 1335 | 0 | 0 |  |  |  |  | if $tn{$name}; | 
| 1336 |  |  |  |  |  |  | Audio::Nama::throw("$name: reserved track name. Skipping"), return | 
| 1337 | 0 | 0 |  |  |  |  | if grep $name eq $_, @{$mastering->{track_names}}; | 
|  | 0 |  |  |  |  |  |  | 
| 1338 |  |  |  |  |  |  |  | 
| 1339 |  |  |  |  |  |  | # in order to increment serially | 
| 1340 | 0 |  |  |  |  |  | Audio::Nama::ChainSetup::remove_temporary_tracks(); | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 | 0 |  |  |  |  |  | my $track = $class->new(%vals); | 
| 1343 | 0 | 0 |  |  |  |  | return if ! $track; | 
| 1344 | 0 |  |  |  |  |  | logpkg(__FILE__,__LINE__,'debug', "ref new track: ", ref $track); | 
| 1345 | 0 | 0 |  |  |  |  | $track->source($gui->{_chr}) if $gui->{_chr}; | 
| 1346 |  |  |  |  |  |  | #		$track->send($gui->{_chm}) if $gui->{_chm}; | 
| 1347 |  |  |  |  |  |  |  | 
| 1348 | 0 |  |  |  |  |  | my $bus = $bn{$track->group}; | 
| 1349 | 0 | 0 | 0 |  |  |  | process_command('for mon; mon') if $mode->{preview} and $bus->rw eq MON; | 
| 1350 |  |  |  |  |  |  | # TODO ??? | 
| 1351 | 0 | 0 |  |  |  |  | $bus->set(rw => MON) unless $track->target; # not if is alias | 
| 1352 |  |  |  |  |  |  |  | 
| 1353 |  |  |  |  |  |  | # normal tracks default to MON | 
| 1354 |  |  |  |  |  |  | # track aliases default to PLAY | 
| 1355 |  |  |  |  |  |  | $track->set(rw => $track->target | 
| 1356 |  |  |  |  |  |  | ?  PLAY | 
| 1357 | 0 | 0 | 0 |  |  |  | :  $config->{new_track_rw} || MON ); | 
| 1358 | 0 |  |  |  |  |  | $gui->{_track_name} = $gui->{_chm} = $gui->{_chr} = undef; | 
| 1359 |  |  |  |  |  |  |  | 
| 1360 | 0 |  |  |  |  |  | set_current_bus(); | 
| 1361 | 0 |  |  | 0 |  |  | logpkg(__FILE__,__LINE__,'debug', "Added new track!\n", sub{$track->dump}); | 
|  | 0 |  |  |  |  |  |  | 
| 1362 | 0 |  |  |  |  |  | $track; | 
| 1363 |  |  |  |  |  |  | } | 
| 1364 |  |  |  |  |  |  |  | 
| 1365 |  |  |  |  |  |  | # create read-only track pointing at WAV files of specified | 
| 1366 |  |  |  |  |  |  | # name in current project | 
| 1367 |  |  |  |  |  |  |  | 
| 1368 |  |  |  |  |  |  | sub add_track_alias { | 
| 1369 | 0 |  |  | 0 |  |  | my ($name, $track) = @_; | 
| 1370 | 0 |  |  |  |  |  | my $target; | 
| 1371 | 0 | 0 |  |  |  |  | if 		( $tn{$track} ){ $target = $track } | 
|  | 0 | 0 |  |  |  |  |  | 
| 1372 | 0 |  |  |  |  |  | elsif	( $ti{$track} ){ $target = $ti{$track}->name } | 
| 1373 | 0 |  |  |  |  |  | add_track(  $name, target => $target, width => $tn{$target}->width); | 
| 1374 |  |  |  |  |  |  | } | 
| 1375 |  |  |  |  |  |  | # create read-only track pointing at WAV files of specified | 
| 1376 |  |  |  |  |  |  | # track name in a different project | 
| 1377 |  |  |  |  |  |  |  | 
| 1378 |  |  |  |  |  |  | sub add_track_alias_project { | 
| 1379 | 0 |  |  | 0 |  |  | my ($name, $track, $project_name) = @_; | 
| 1380 | 0 |  | 0 |  |  |  | $project_name //= $Audio::Nama::project->{name}; | 
| 1381 | 0 |  |  |  |  |  | my $dir =  join_path(project_root(), $project_name, '.wav'); | 
| 1382 | 0 | 0 |  |  |  |  | if ( -d $dir ){ | 
| 1383 | 0 | 0 |  |  |  |  | if ( glob "$dir/$track*.wav"){ | 
| 1384 | 0 |  |  |  |  |  | Audio::Nama::pager("Found target WAV files.\n"); | 
| 1385 | 0 |  |  |  |  |  | my @params = ( | 
| 1386 |  |  |  |  |  |  | target => $track, | 
| 1387 |  |  |  |  |  |  | project => $project_name, | 
| 1388 |  |  |  |  |  |  | ); | 
| 1389 | 0 |  |  |  |  |  | add_track( $name, @params ); | 
| 1390 | 0 |  |  |  |  |  | } else { Audio::Nama::throw("$project_name:$track - No WAV files found.  Skipping.\n"), return; } | 
| 1391 |  |  |  |  |  |  | } else { | 
| 1392 | 0 |  |  |  |  |  | Audio::Nama::throw("$project_name: project does not exist.  Skipping.\n"); | 
| 1393 | 0 |  |  |  |  |  | return; | 
| 1394 |  |  |  |  |  |  | } | 
| 1395 |  |  |  |  |  |  | } | 
| 1396 |  |  |  |  |  |  |  | 
| 1397 |  |  |  |  |  |  | # vol/pan requirements of mastering and mixdown tracks | 
| 1398 |  |  |  |  |  |  |  | 
| 1399 |  |  |  |  |  |  | # called from Track_subs, Graphical_subs | 
| 1400 |  |  |  |  |  |  | { my %volpan = ( | 
| 1401 |  |  |  |  |  |  | Eq => {}, | 
| 1402 |  |  |  |  |  |  | Low => {}, | 
| 1403 |  |  |  |  |  |  | Mid => {}, | 
| 1404 |  |  |  |  |  |  | High => {}, | 
| 1405 |  |  |  |  |  |  | Boost => {vol => 1}, | 
| 1406 |  |  |  |  |  |  | Mixdown => {}, | 
| 1407 |  |  |  |  |  |  | ); | 
| 1408 |  |  |  |  |  |  |  | 
| 1409 |  |  |  |  |  |  | sub need_vol_pan { | 
| 1410 |  |  |  |  |  |  |  | 
| 1411 |  |  |  |  |  |  | # this routine used by | 
| 1412 |  |  |  |  |  |  | # | 
| 1413 |  |  |  |  |  |  | # + add_track() to determine whether a new track _will_ need vol/pan controls | 
| 1414 |  |  |  |  |  |  | # + add_track_gui() to determine whether an existing track needs vol/pan | 
| 1415 |  |  |  |  |  |  |  | 
| 1416 | 0 |  |  | 0 |  |  | my ($track_name, $type) = @_; | 
| 1417 |  |  |  |  |  |  |  | 
| 1418 |  |  |  |  |  |  | # $type: vol | pan | 
| 1419 |  |  |  |  |  |  |  | 
| 1420 |  |  |  |  |  |  | # Case 1: track already exists | 
| 1421 |  |  |  |  |  |  |  | 
| 1422 | 0 | 0 | 0 |  |  |  | return 1 if $tn{$track_name} and $tn{$track_name}->$type; | 
| 1423 |  |  |  |  |  |  |  | 
| 1424 |  |  |  |  |  |  | # Case 2: track not yet created | 
| 1425 |  |  |  |  |  |  |  | 
| 1426 | 0 | 0 |  |  |  |  | if( $volpan{$track_name} ){ | 
| 1427 | 0 | 0 |  |  |  |  | return($volpan{$track_name}{$type}	? 1 : 0 ) | 
| 1428 |  |  |  |  |  |  | } | 
| 1429 | 0 |  |  |  |  |  | return 1; | 
| 1430 |  |  |  |  |  |  | } | 
| 1431 |  |  |  |  |  |  | } | 
| 1432 |  |  |  |  |  |  |  | 
| 1433 |  |  |  |  |  |  | # track width in words | 
| 1434 |  |  |  |  |  |  |  | 
| 1435 |  |  |  |  |  |  | sub width { | 
| 1436 | 0 |  |  | 0 |  |  | my $count = shift; | 
| 1437 | 0 | 0 |  |  |  |  | return 'mono' if $count == 1; | 
| 1438 | 0 | 0 |  |  |  |  | return 'stereo' if $count == 2; | 
| 1439 | 0 |  |  |  |  |  | return "$count channels"; | 
| 1440 |  |  |  |  |  |  | } | 
| 1441 |  |  |  |  |  |  |  | 
| 1442 |  |  |  |  |  |  |  | 
| 1443 |  |  |  |  |  |  | sub add_volume_control { | 
| 1444 | 0 |  |  | 0 |  |  | my $n = shift; | 
| 1445 | 0 | 0 |  |  |  |  | return unless need_vol_pan($ti{$n}->name, "vol"); | 
| 1446 |  |  |  |  |  |  |  | 
| 1447 |  |  |  |  |  |  | my $vol_id = Audio::Nama::Effect->new( | 
| 1448 |  |  |  |  |  |  | chain => $n, | 
| 1449 |  |  |  |  |  |  | type => $config->{volume_control_operator}, | 
| 1450 | 0 |  |  |  |  |  | id => $ti{$n}->vol, # often undefined | 
| 1451 |  |  |  |  |  |  | )->id; | 
| 1452 |  |  |  |  |  |  |  | 
| 1453 | 0 |  |  |  |  |  | $ti{$n}->set(vol => $vol_id);  # save the id for next time | 
| 1454 | 0 |  |  |  |  |  | $vol_id; | 
| 1455 |  |  |  |  |  |  | } | 
| 1456 |  |  |  |  |  |  | sub add_pan_control { | 
| 1457 | 0 |  |  | 0 |  |  | my $n = shift; | 
| 1458 | 0 | 0 |  |  |  |  | return unless need_vol_pan($ti{$n}->name, "pan"); | 
| 1459 |  |  |  |  |  |  |  | 
| 1460 |  |  |  |  |  |  | my $pan_id = Audio::Nama::Effect->new( | 
| 1461 |  |  |  |  |  |  | chain => $n, | 
| 1462 |  |  |  |  |  |  | type => 'epp', | 
| 1463 | 0 |  |  |  |  |  | id => $ti{$n}->pan, # often undefined | 
| 1464 |  |  |  |  |  |  | )->id; | 
| 1465 |  |  |  |  |  |  |  | 
| 1466 | 0 |  |  |  |  |  | $ti{$n}->set(pan => $pan_id);  # save the id for next time | 
| 1467 | 0 |  |  |  |  |  | $pan_id; | 
| 1468 |  |  |  |  |  |  | } | 
| 1469 |  |  |  |  |  |  | sub rename_track { | 
| 1470 | 1 |  |  | 1 |  | 5 | use Cwd; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 54 |  | 
| 1471 | 1 |  |  | 1 |  | 5 | use File::Slurp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 696 |  | 
| 1472 | 0 |  |  | 0 |  |  | my ($oldname, $newname, $statefile, $dir) = @_; | 
| 1473 | 0 |  |  |  |  |  | save_state(); | 
| 1474 | 0 |  |  |  |  |  | my $old_dir = cwd(); | 
| 1475 | 0 |  |  |  |  |  | chdir $dir; | 
| 1476 |  |  |  |  |  |  |  | 
| 1477 |  |  |  |  |  |  | # rename audio files | 
| 1478 |  |  |  |  |  |  |  | 
| 1479 | 0 |  |  |  |  |  | qx(rename 's/^$oldname(?=[_.])/$newname/' *.wav); | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 |  |  |  |  |  |  |  | 
| 1482 |  |  |  |  |  |  | # rename in State.json when candidate key | 
| 1483 |  |  |  |  |  |  | # is part of the specified set and the value | 
| 1484 |  |  |  |  |  |  | # exactly matches $oldname | 
| 1485 |  |  |  |  |  |  |  | 
| 1486 | 0 |  |  |  |  |  | my $state = read_file($statefile); | 
| 1487 |  |  |  |  |  |  |  | 
| 1488 | 0 |  |  |  |  |  | $state =~ s/ | 
| 1489 |  |  |  |  |  |  | "					# open quote | 
| 1490 |  |  |  |  |  |  | (track| 		# one of specified fields | 
| 1491 |  |  |  |  |  |  | name| | 
| 1492 |  |  |  |  |  |  | group| | 
| 1493 |  |  |  |  |  |  | source| | 
| 1494 |  |  |  |  |  |  | send_id| | 
| 1495 |  |  |  |  |  |  | target| | 
| 1496 |  |  |  |  |  |  | current_edit| | 
| 1497 |  |  |  |  |  |  | send_id| | 
| 1498 |  |  |  |  |  |  | return_id| | 
| 1499 |  |  |  |  |  |  | wet_track| | 
| 1500 |  |  |  |  |  |  | dry_track| | 
| 1501 |  |  |  |  |  |  | track| | 
| 1502 |  |  |  |  |  |  | host_track) | 
| 1503 |  |  |  |  |  |  | "				# close quote | 
| 1504 |  |  |  |  |  |  | \ 				# space | 
| 1505 |  |  |  |  |  |  | :				# colon | 
| 1506 |  |  |  |  |  |  | \ 				# space | 
| 1507 |  |  |  |  |  |  | "$oldname"/"$1" : "$newname"/gx; | 
| 1508 |  |  |  |  |  |  |  | 
| 1509 | 0 |  |  |  |  |  | write_file($statefile, $state); | 
| 1510 | 0 |  |  |  |  |  | my $msg = "Rename track $oldname -> $newname"; | 
| 1511 | 0 |  |  |  |  |  | git_commit($msg); | 
| 1512 | 0 |  |  |  |  |  | Audio::Nama::pager($msg); | 
| 1513 | 0 |  |  |  |  |  | load_project(name => $Audio::Nama::project->{name}); | 
| 1514 |  |  |  |  |  |  | } | 
| 1515 |  |  |  |  |  |  | sub user_tracks_present { | 
| 1516 | 0 |  |  | 0 |  |  | my $i = 0; | 
| 1517 | 0 |  |  |  |  |  | $i++ for user_tracks(); | 
| 1518 | 0 |  |  |  |  |  | $i | 
| 1519 |  |  |  |  |  |  | } | 
| 1520 | 0 |  |  | 0 |  |  | sub all_tracks { sort{$a->n <=> $b->n } values %Audio::Nama::Track::by_name } | 
|  | 0 |  |  |  |  |  |  | 
| 1521 | 0 |  |  | 0 |  |  | sub audio_tracks { grep { $_->class !~ /Midi/ } all_tracks() } | 
|  | 0 |  |  |  |  |  |  | 
| 1522 | 0 |  |  | 0 |  |  | sub midi_tracks { grep { $_->class =~ /Midi/ } all_tracks() } | 
|  | 0 |  |  |  |  |  |  | 
| 1523 |  |  |  |  |  |  | sub rec_hookable_tracks { | 
| 1524 | 0 | 0 |  | 0 |  |  | grep{ $_->group ne 'Temp' and $_->group ne 'Insert' } all_tracks() | 
|  | 0 |  |  |  |  |  |  | 
| 1525 |  |  |  |  |  |  | } | 
| 1526 | 0 |  |  | 0 |  |  | sub user_tracks { grep { ! $_->is_system_track } all_tracks() } | 
|  | 0 |  |  |  |  |  |  | 
| 1527 | 0 |  |  | 0 |  |  | sub system_tracks { grep { $_->is_system_track } all_tracks() } | 
|  | 0 |  |  |  |  |  |  | 
| 1528 |  |  |  |  |  |  | } # end package | 
| 1529 |  |  |  |  |  |  | { | 
| 1530 |  |  |  |  |  |  | package Audio::Nama::MidiTrack; | 
| 1531 | 1 |  |  | 1 |  | 6 | use Audio::Nama::Globals qw(:all); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 456 |  | 
| 1532 | 1 |  |  | 1 |  | 5 | use Audio::Nama::Log qw(logpkg); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 121 |  | 
| 1533 |  |  |  |  |  |  | our @ISA = qw(Audio::Nama::Track); | 
| 1534 |  |  |  |  |  |  | } | 
| 1535 |  |  |  |  |  |  |  | 
| 1536 |  |  |  |  |  |  | 1; | 
| 1537 |  |  |  |  |  |  | __END__ |