File Coverage

blib/lib/Audio/Nama/IO.pm
Criterion Covered Total %
statement 147 309 47.5
branch 0 78 0.0
condition 0 33 0.0
subroutine 49 121 40.5
pod 0 21 0.0
total 196 562 34.8


line stmt bran cond sub pod time code
1             package Audio::Nama;
2             our (%tn, $jack, $config);
3              
4             # ---------- IO -----------
5              
6             #
7             # IO objects for writing Ecasound chain setup file
8             #
9             # Object values can come from three sources:
10             #
11             # 1. As arguments to the constructor new() while walking the
12             # routing graph:
13             # + assigned by dispatch: chain_id, loop_id, track, etc.
14             # + override by graph node (higher priority)
15             # + override by graph edge (highest priority)
16             # 2. (sub)class methods called as $object->method_name
17             # + defined as _method_name (access via AUTOLOAD, overrideable by constructor)
18             # + defined as method_name (not overrideable)
19             # 3. AUTOLOAD
20             # + any other method calls are passed to the the associated track
21             # + illegal track method call generate an exception
22              
23             package Audio::Nama::IO;
24 2     2   22666 use Modern::Perl; use Carp;
  2     2   13455  
  2         13  
  2         283  
  2         4  
  2         109  
25 2     2   841 use Data::Dumper::Concise;
  2         12668  
  2         228  
26             our $VERSION = 1.0;
27              
28             # provide following vars to all packages
29             our ($config, $jack, %tn);
30             our (%by_name); # index for $by_name{trackname}->{input} = $object
31 2     2   578 use Audio::Nama::Globals qw($config $jack %tn $setup :trackrw);
  2         5  
  2         384  
32 2     2   812 use Try::Tiny;
  2         1485  
  2         305  
33              
34 0     0 0   sub initialize { %by_name = () }
35              
36             # we will use the following to map from graph node names
37             # to IO class names
38              
39             our %io_class = qw(
40             null_in Audio::Nama::IO::from_null
41             null_out Audio::Nama::IO::to_null
42             soundcard_in Audio::Nama::IO::from_soundcard
43             soundcard_out Audio::Nama::IO::to_soundcard
44             soundcard_device_in Audio::Nama::IO::from_alsa_soundcard_device
45             soundcard_device_out Audio::Nama::IO::to_alsa_soundcard_device
46             wav_in Audio::Nama::IO::from_wav
47             wav_out Audio::Nama::IO::to_wav
48             loop_source Audio::Nama::IO::from_loop
49             loop_sink Audio::Nama::IO::to_loop
50             jack_manual_in Audio::Nama::IO::from_jack_port
51             jack_manual_out Audio::Nama::IO::to_jack_port
52             jack_ports_list_in Audio::Nama::IO::from_jack_port
53             jack_ports_list_out Audio::Nama::IO::to_jack_port
54             jack_multi_in Audio::Nama::IO::from_jack_multi
55             jack_multi_out Audio::Nama::IO::to_jack_multi
56             jack_client_in Audio::Nama::IO::from_jack_client
57             jack_client_out Audio::Nama::IO::to_jack_client
58             );
59              
60             ### class descriptions
61              
62             # === CLASS Audio::Nama::IO::from_jack_port ===
63             #
64             # is triggered by source_type codes:
65             #
66             # + jack_manual_in
67             # + jack_ports_list_in
68             #
69             # For track 'piano', the class creates an input similar to:
70             #
71             # -i:jack,,piano_in
72             #
73             # which receives input from JACK node:
74             #
75             # + Nama:piano_in,
76             #
77             # If piano is stereo, the actual ports will be:
78             #
79             # + Nama:piano_in_1
80             # + Nama:piano_in_2
81              
82             # (CLASS Audio::Nama::IO::to_jack_port is similar)
83              
84             ### class definition
85              
86             our $AUTOLOAD;
87              
88             # add underscore to field names so that regular method
89             # access will go through AUTOLOAD
90              
91             # we add an underscore to each key
92              
93 2     2   517 use Audio::Nama::Object qw(track_ chain_id_ endpoint_ format_ format_template_ width_ ecs_extra_ direction_ device_id_);
  2         5  
  2         15  
94              
95             sub new {
96 0     0 0   my $class = shift;
97 0           my %vals = @_;
98 0           my @args = map{$_."_", $vals{$_}} keys %vals; # add underscore to key
  0            
99              
100             # note that we won't check for illegal fields
101             # so we can pass any value and allow AUTOLOAD to
102             # check the hash for it.
103            
104 0           my $self = bless {@args}, $class;
105              
106 0           my $direction = $self->direction; # input or output
107              
108             # join IO objects to graph
109 0           my $name;
110 0     0     try{ $name = $self->name }
111 0     0     catch {}; # we do nothing
112              
113 2     2   12 { no warnings 'uninitialized';
  2         5  
  2         1937  
  0            
114             Audio::Nama::logpkg(__FILE__,__LINE__,'debug',"I belong to track $name\n",
115 0     0     sub{Dumper($self)} );
  0            
116             }
117            
118 0 0         if($name){
119 0           $by_name{$name}->{$direction} = $self;
120             }
121             $self
122 0           }
123              
124             # latency stubs
125             sub capture_latency {
126 0     0 0   my $self = shift;
127 0 0         return unless $self->client;
128 0           Audio::Nama::jack_port_latency('input', $self->client);
129             }
130             sub playback_latency {
131 0     0 0   my $self = shift;
132 0 0         return unless $self->client;
133 0           Audio::Nama::jack_port_latency('output', $self->client);
134             }
135              
136             # we need at least stubs for subclasses' methods
137             # for AUTOLOAD to be happy - so we include
138              
139       0 0   sub client {}
140              
141             #### JACK related methods
142              
143             # inherited by all, the methods defined below are called in
144             # these classes:
145             #
146             # to_jack_multi,
147             # from_jack_multi
148             # to_jack_client
149             # from_jack_client
150             #
151             # They have no function in other classes.
152              
153              
154             sub target_id {
155 0     0 0   my $self = shift;
156 0 0         $self->direction eq 'input'
157             ? $self->source_id
158             : $self->send_id;
159             }
160             sub target_type {
161 0     0 0   my $self = shift;
162 0 0         $self->direction eq 'input'
163             ? $self->source_type
164             : $self->send_type;
165             }
166             sub target_channel {
167 0     0 0   my $self = shift;
168 0 0         $self->target_id =~ /^(\d+)$/ ? $1 : 1
169             }
170             sub ports {
171 0     0 0   my $self = shift;
172 0 0         my $client_direction = $self->direction eq 'input' ? 'output' : 'input';
173             Audio::Nama::IO::jack_multi_ports( $self->client,
174             $client_direction,
175             $self->target_channel,
176             $self->width,
177 0     0     Audio::Nama::try{$self->name}
178 0 0         ) if $self->client
179             }
180              
181              
182              
183             sub ecs_string {
184 0     0 0   my $self = shift;
185 0           my @parts;
186 0 0         push @parts, '-f:'.$self->format if $self->format;
187 0           push @parts, '-'.$self->io_prefix.':'.$self->device_id;
188 0           join ' ',@parts;
189             }
190              
191             ## the format() method generates the correct Ecasound format string,
192             ## (e.g. -f:f32_le,2,48000) if the _format_template() method
193             ## returns a signal format template (e.g. f32_le,N,48000)
194              
195             sub _format {
196 0     0     my $self = shift;
197 0 0 0       Audio::Nama::signal_format($self->format_template, $self->width)
198             if $self->format_template and $self->width
199             }
200       0     sub _format_template {} # the leading underscore allows override
201             # by a method without the underscore
202              
203       0     sub _ecs_extra {} # allow override
204             sub direction {
205 0 0   0 0   (ref $_[0]) =~ /::from/ ? 'input' : 'output'
206             }
207 0     0 0   sub io_prefix { substr $_[0]->direction, 0, 1 } # 'i' or 'o'
208              
209             sub AUTOLOAD {
210 0     0     my $self = shift;
211             # get tail of method call
212 0           my ($call) = $AUTOLOAD =~ /([^:]+)$/;
213 0           my $result = q();
214 0           my $field = "$call\_";
215 0           my $method = "_$call";
216 0 0         return $self->{$field} if exists $self->{$field};
217 0 0         return $self->$method if $self->can($method);
218 2     2   18 { no warnings 'uninitialized';
  2         4  
  2         1049  
  0            
219 0 0         if ( my $track = $tn{$self->{track_}} ){
220 0 0         return $track->$call if $track->can($call)
221             # ->can is reliable here because Track has no AUTOLOAD
222             }
223             # suppress error XXX
224 0 0 0       return undef if $call eq 'name' or $call eq 'surname';
225             }
226 0           my $msg = "Autoload fell through. Object type: ". (ref $self). " illegal method call: $call\n";
227 0           Audio::Nama::throw($msg,$self->dump);
228 0           croak
229             }
230              
231       0     sub DESTROY {}
232              
233             sub _mono_to_stereo{
234            
235             # copy mono track to stereo if we have a pan control and a mono source
236              
237             # Truth table
238             #REC status, Track width stereo: null
239             #REC status, Track width mono: chcopy
240             #PLAY status, WAV width mono: chcopy
241             #PLAY status, WAV width stereo: null
242             #Higher channel count (WAV or Track): null
243              
244 0     0     my $self = shift;
245 0           my $status = $self->rec_status();
246 0           my $copy = "-chcopy:1,2";
247 0           my $nocopy = "";
248 0     0     my $is_mono_track = sub { $self->width == 1 };
  0            
249 0     0     my $is_mono_wav = sub { Audio::Nama::channels($self->wav_format) == 1};
  0            
250 0 0 0       if (
      0        
      0        
251             ($self->track and $tn{$self->track}->pan)
252             and
253             ( $status =~ /REC|MON/ and $is_mono_track->()
254             or $status eq PLAY and $is_mono_wav->() )
255             )
256 0           { $copy } else { $nocopy }
  0            
257             }
258             sub _playat_output {
259 0     0     my $track = shift;
260 0 0         return unless $track->shifted_playat_time;
261             # or $track->latency_offset;
262 0           join ',',"playat" , $track->shifted_playat_time
263             # + $track->latency_offset
264             }
265             sub _select_output {
266 0     0     my $track = shift;
267 2     2   12 no warnings 'uninitialized';
  2         4  
  2         970  
268 0           my $start = $track->shifted_region_start_time + $config->hardware_latency();
269 0           my $end = $track->shifted_region_end_time;
270 0 0 0       return unless $config->hardware_latency() or defined $start and defined $end;
      0        
271 0           my $setup_length;
272             # CASE 1: a region is defined
273 0 0         if ($end) {
274 0           $setup_length = $end - $start;
275             }
276             # CASE 2: only hardware latency
277             else {
278 0           $setup_length = $track->wav_length - $start
279             }
280 0           join ',',"select", $start, $setup_length
281             }
282             ### utility subroutines
283              
284             sub get_class {
285 0     0 0   my ($type,$direction) = @_;
286             Audio::Nama::Graph::is_a_loop($type) and
287 0 0         return $io_class{ $direction eq 'input' ? "loop_source" : "loop_sink"};
    0          
288 0 0         $io_class{$type} or croak "unrecognized IO type: $type"
289             }
290             sub soundcard_input_type_string {
291 0 0   0 0   $jack->{jackd_running} ? 'jack_multi_in' : 'soundcard_device_in'
292             }
293             sub soundcard_output_type_string {
294 0 0   0 0   $jack->{jackd_running} ? 'jack_multi_out' : 'soundcard_device_out'
295             }
296             sub soundcard_input_device_string {
297             $jack->{jackd_running} ? 'system' : $config->{alsa_capture_device}
298 0 0   0 0   }
299             sub soundcard_output_device_string {
300             $jack->{jackd_running} ? 'system' : $config->{alsa_playback_device}
301 0 0   0 0   }
302              
303             sub jack_multi_route {
304 0     0 0   my (@ports) = @_;
305             join q(,),q(jack_multi),
306 0           map{quote_jack_port($_)} @ports
  0            
307             }
308              
309             sub jack_multi_ports {
310 0     0 0   my ($client, $direction, $start, $width, $trackname) = @_;
311 2     2   11 no warnings 'uninitialized';
  2         4  
  2         802  
312 0           Audio::Nama::logpkg(__FILE__,__LINE__,'debug',"trackname: $trackname, client $client, direction $direction, start: $start, width $width");
313              
314             # can we route to these channels?
315 0           my $end = $start + $width - 1;
316              
317             # the following logic avoids deferencing undef for a
318             # non-existent client, and correctly handles
319             # the case of a portname (containing colon)
320            
321 0           my $channel_count = scalar @{$jack->{clients}->{$client}{$direction}};
  0            
322 0 0         my $source_or_send = $direction eq 'input' ? 'send' : 'source';
323             die(qq(
324             Track $trackname: $source_or_send would cover channels $start - $end,
325             out of bounds for JACK client "$client" ($channel_count channels max).
326             Change $source_or_send setting, or set track OFF.))
327 0 0 0       if $end > $channel_count and $config->{enforce_channel_bounds};
328 0           return @{$jack->{clients}->{$client}{$direction}}[$start-1..$end-1]
329 0 0         if $jack->{clients}->{$client}{$direction};
330              
331             }
332             #sub one_port { $jack->{clients}->{$client}->{$direction}->[$start-1] }
333              
334             sub quote_jack_port {
335 0     0 0   my $port = shift;
336 0 0 0       ($port =~ /\s/ and $port !~ /^"/) ? qq("$port") : $port
337             }
338             sub rectified { # client name from number
339 0 0   0 0   $_[0] =~ /^\d+$/
340             ? 'system'
341             : $_[0]
342             }
343             ### subclass definitions
344              
345             ### method names with a preceding underscore
346             ### can be overridded by the object constructor
347              
348             {
349             package Audio::Nama::IO::from_null;
350 2     2   12 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   3  
  2         14  
  2         193  
  2         4  
  2         181  
351 0     0     sub _device_id { 'null' }
352             }
353              
354             {
355             package Audio::Nama::IO::to_null;
356 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   4  
  2         8  
  2         132  
  2         4  
  2         187  
357 0     0     sub _device_id { 'null' }
358             }
359              
360             {
361             package Audio::Nama::IO::from_rtnull;
362 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   5  
  2         12  
  2         140  
  2         9  
  2         199  
363 0     0     sub _device_id { 'rtnull' }
364             }
365              
366             {
367             package Audio::Nama::IO::to_rtnull;
368 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   5  
  2         8  
  2         133  
  2         5  
  2         166  
369 0     0     sub _device_id { 'rtnull' }
370             }
371              
372             {
373             package Audio::Nama::IO::from_wav;
374 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   2  
  2         10  
  2         126  
  2         5  
  2         543  
375             sub device_id {
376 0     0     my $self = shift;
377 0           my @modifiers;
378 0 0         push @modifiers, $self->playat_output if $self->playat_output;
379 0 0         push @modifiers, $self->select_output if $self->select_output;
380 0 0         push @modifiers, split " ", $self->modifiers if $self->modifiers;
381 0           push @modifiers, $self->full_path;
382 0           join(q[,],@modifiers);
383             }
384 0     0     sub ecs_extra { $_[0]->mono_to_stereo}
385 0 0   0     sub client { 'system' if $jack->{jackd_running} } # since we share latency value
386 0     0     sub ports { 'system:capture_1' }
387             }
388             {
389             package Audio::Nama::IO::to_wav;
390 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   4  
  2         9  
  2         148  
  2         4  
  2         248  
391 0     0     sub device_id { $_[0]->full_path }
392 0     0     sub _format_template { $config->{raw_to_disk_format} }
393             }
394              
395             {
396             package Audio::Nama::IO::from_loop;
397 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   42  
  2         8  
  2         140  
  2         28  
  2         273  
398             sub new {
399 0     0     my $class = shift;
400 0           my %vals = @_;
401 0           $class->SUPER::new( %vals, device_id => "loop,$vals{endpoint}");
402             }
403       0     sub format {}
404             }
405             {
406             package Audio::Nama::IO::to_loop;
407 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO::from_loop';
  2     2   4  
  2         8  
  2         136  
  2         2  
  2         158  
408             }
409              
410             {
411             package Audio::Nama::IO::from_soundcard;
412 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   3  
  2         13  
  2         133  
  2         4  
  2         272  
413             sub new {
414 0     0     shift; # throw away class
415 0           my $class = $io_class{Audio::Nama::IO::soundcard_input_type_string()};
416 0           $class->new(@_);
417             }
418             }
419             {
420             package Audio::Nama::IO::to_soundcard;
421 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   5  
  2         8  
  2         168  
  2         6  
  2         231  
422             sub new {
423 0     0     shift; # throw away class
424 0           my $class = $io_class{Audio::Nama::IO::soundcard_output_type_string()};
425 0           $class->new(@_);
426             }
427             }
428             {
429             package Audio::Nama::IO::to_jack_multi;
430 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   4  
  2         38  
  2         161  
  2         3  
  2         333  
431             sub client {
432 0     0     my $self = shift;
433             # say "to_jack_multi: target_id: ",$self->target_id;
434             # say "to_jack_multi: rectified target_id: ",Audio::Nama::IO::rectified($self->target_id);
435 0           Audio::Nama::IO::rectified($self->target_id)
436             }
437             sub device_id {
438 0     0     my $self = shift;
439 0           Audio::Nama::IO::jack_multi_route($self->ports)
440             }
441             }
442              
443             {
444             package Audio::Nama::IO::from_jack_multi;
445 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO::to_jack_multi';
  2     2   10  
  2         10  
  2         163  
  2         4  
  2         182  
446 0     0     sub ecs_extra { $_[0]->mono_to_stereo }
447             }
448              
449             {
450             package Audio::Nama::IO::to_jack_port;
451 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   3  
  2         9  
  2         131  
  2         5  
  2         446  
452 0     0     sub format_template { $config->{devices}->{jack}->{signal_format} }
453 0     0     sub device_id { 'jack,,'.$_[0]->port_name.'_out' }
454 0     0     sub ports { "Nama:".$_[0]->port_name. '_out_1' } # at least this one port
455             # HARDCODED port name
456             }
457              
458             {
459             package Audio::Nama::IO::from_jack_port;
460 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO::to_jack_port';
  2     2   3  
  2         9  
  2         139  
  2         5  
  2         298  
461 0     0     sub device_id { 'jack,,'.$_[0]->port_name.'_in' }
462 0     0     sub ecs_extra { $_[0]->mono_to_stereo }
463 0     0     sub ports { "Nama:".$_[0]->port_name. '_in_1' } # at least this one port
464             # HARDCODED port name
465             }
466              
467             {
468             package Audio::Nama::IO::to_jack_client;
469 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   6  
  2         9  
  2         151  
  2         5  
  2         322  
470 0     0     sub device_id { "jack," . Audio::Nama::IO::quote_jack_port($_[0]->send_id); }
471 0     0     sub client { Audio::Nama::IO::rectified($_[0]->send_id) }
472             }
473              
474             {
475             package Audio::Nama::IO::from_jack_client;
476 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   3  
  2         10  
  2         135  
  2         3  
  2         335  
477 0     0     sub device_id { 'jack,'. Audio::Nama::IO::quote_jack_port($_[0]->source_id); }
478 0     0     sub ecs_extra { $_[0]->mono_to_stereo}
479 0     0     sub client { Audio::Nama::IO::rectified($_[0]->source_id) }
480             }
481              
482             {
483             package Audio::Nama::IO::from_alsa_soundcard_device;
484 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   3  
  2         8  
  2         137  
  2         4  
  2         365  
485 0     0     sub ecs_extra { join ' ', $_[0]->rec_route, $_[0]->mono_to_stereo }
486 0     0     sub device_id { $config->{devices}->{$config->{alsa_capture_device}}->{ecasound_id} }
487 0     0     sub input_channel { $_[0]->source_id }
488             sub rec_route {
489             # works for mono/stereo only!
490 2     2   11 no warnings qw(uninitialized);
  2         3  
  2         311  
491 0     0     my $self = shift;
492             # needed only if input channel is greater than 1
493 0 0 0       return '' if ! $self->input_channel or $self->input_channel == 1;
494            
495 0           my $route = "-chmove:" . $self->input_channel . ",1";
496 0 0         if ( $self->width == 2){
497 0           $route .= " -chmove:" . ($self->input_channel + 1) . ",2";
498             }
499 0           return $route;
500             }
501             }
502             {
503             package Audio::Nama::IO::to_alsa_soundcard_device;
504 2     2   10 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   4  
  2         9  
  2         141  
  2         4  
  2         674  
505 0     0     sub device_id { $config->{devices}->{$config->{alsa_playback_device}}{ecasound_id} }
506 0     0     sub ecs_extra {route($_[0]->width,$_[0]->output_channel) }
507 0     0     sub output_channel { $_[0]->send_id }
508             sub route2 {
509 0     0     my ($from, $to, $width) = @_;
510             }
511             sub route {
512             # routes signals (1..$width) to ($dest..$dest+$width-1 )
513            
514 0     0     my ($width, $dest) = @_;
515 0 0 0       return '' if ! $dest or $dest == 1;
516             # print "route: width: $width, destination: $dest\n\n";
517 0           my $offset = $dest - 1;
518 0           my $route ;
519 0           for my $c ( map{$width - $_ + 1} 1..$width ) {
  0            
520 0           $route .= " -chmove:$c," . ( $c + $offset);
521             }
522 0           $route;
523             }
524             }
525             {
526             package Audio::Nama::IO::any;
527 2     2   11 use Modern::Perl; use vars qw(@ISA); @ISA = 'Audio::Nama::IO';
  2     2   5  
  2         8  
  2         141  
  2         5  
  2         241  
528             }
529              
530              
531             1;
532             __END__