File Coverage

blib/lib/Audio/Nama/Track.pm
Criterion Covered Total %
statement 196 870 22.5
branch 0 328 0.0
condition 0 87 0.0
subroutine 65 238 27.3
pod 0 110 0.0
total 261 1633 15.9


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__