File Coverage

blib/lib/Audio/Nama/Initializations.pm
Criterion Covered Total %
statement 28 83 33.7
branch 0 20 0.0
condition 0 3 0.0
subroutine 10 28 35.7
pod 0 4 0.0
total 38 138 27.5


line stmt bran cond sub pod time code
1             # ----------- Initialize --------
2             #
3             #
4             # These routines are executed once on program startup
5             #
6             #
7              
8             package Audio::Nama;
9 1     1   5 use Modern::Perl; use Carp;
  1     1   2  
  1         6  
  1         123  
  1         1  
  1         54  
10 1     1   5 use Socket qw(getnameinfo NI_NUMERICHOST) ;
  1         3  
  1         640  
11              
12 0     0     sub is_test_script { $config->{opts}->{J} }
13             # if we are using fake JACK client data,
14             # probably a test script is running
15              
16             sub apply_test_args {
17              
18             push @ARGV, qw(-f /dev/null), # force to use internal namarc
19              
20             qw(-t), # set text mode
21              
22             qw(-d), $Audio::Nama::test_dir,
23            
24             q(-E), # suppress loading Ecasound
25              
26             q(-J), # fake jack client data
27              
28             q(-T), # don't initialize terminal
29             # load fake effects cache
30             # q(-c), 'test-project',
31              
32             #qw(-L SUB); # logging
33              
34 0     0     $jack->{periodsize} = 1024;
35             }
36             sub apply_ecasound_test_args {
37 0     0     apply_test_args();
38 0           @ARGV = grep { $_ ne q(-E) } @ARGV
  0            
39             }
40              
41             sub definitions {
42              
43 0     0     $| = 1; # flush STDOUT buffer on every write
44              
45 0 0         $ui eq 'bullwinkle' or die "no \$ui, bullwinkle";
46              
47 0           @global_effect_chain_vars = qw(
48             @global_effect_chain_data
49             $Audio::Nama::EffectChain::n
50             $fx->{alias}
51             );
52 0           @tracked_vars = qw(
53             @tracks_data
54             @bus_data
55             @groups_data
56             @marks_data
57             @fade_data
58             @edit_data
59             @inserts_data
60             @effects_data
61             $project->{save_file_version_number}
62             $fx->{applied}
63             $fx->{params}
64             $fx->{params_log}
65             );
66 0           @persistent_vars = qw(
67             $project->{save_file_version_number}
68             $project->{timebase}
69             $project->{command_buffer}
70             $project->{track_version_comments}
71             $project->{track_comments}
72             $project->{bunch}
73             $project->{current_op}
74             $project->{current_param}
75             $project->{current_stepsize}
76             $project->{playback_position}
77             @project_effect_chain_data
78             $fx->{id_counter}
79             $setup->{loop_endpoints}
80             $mode->{loop_enable}
81             $mode->{mastering}
82             $mode->{preview}
83             $mode->{midish_terminal}
84             $mode->{midish_transport_sync}
85             $gui->{_seek_unit}
86             $text->{command_history}
87             $this_track_name
88             $this_op
89             );
90              
91              
92 0           $text->{wrap} = new Text::Format {
93             columns => 75,
94             firstIndent => 0,
95             bodyIndent => 0,
96             tabstop => 4,
97             };
98              
99             ####### Initialize singletons #######
100              
101             # Some of these "singletons" (imported by 'use Globals')
102             # are just hashes, some have object behavior as
103             # the sole instance of their class.
104            
105 0           $project = bless {}, 'Audio::Nama::Project';
106 0           $mode = bless {}, 'Audio::Nama::Mode';
107 0           { package Audio::Nama::Mode;
108 0 0   0     sub mastering { $Audio::Nama::tn{Eq} and ! $Audio::Nama::tn{Eq}->{hide} }
109 1     1   6 no warnings 'uninitialized';
  1         2  
  1         220  
110 0     0     sub eager { $Audio::Nama::mode->{eager} }
111             sub doodle {
112             #my $set = shift;
113             #if (defined $set){ $Audio::Nama::mode->{preview} = $set ? 'doodle' : 0 }
114 0     0     $Audio::Nama::mode->{preview} eq 'doodle' }
115 0     0     sub preview { $Audio::Nama::mode->{preview} eq 'preview' }
116 0 0   0     sub song { $Audio::Nama::mode->eager and $Audio::Nama::mode->preview }
117 0 0   0     sub live { $Audio::Nama::mode->eager and $Audio::Nama::mode->doodle }
118             }
119             # for example, $file belongs to class Audio::Nama::File, and uses
120             # AUTOLOAD to generate methods to provide full path
121             # to various system files, such as $file->state_store
122             {
123 0           package Audio::Nama::File;
  0            
124 1     1   10 use Carp;
  1         2  
  1         558  
125             sub logfile {
126 0     0     my $self = shift;
127 0 0         $ENV{NAMA_LOGFILE} || $self->_logfile
128             }
129             sub AUTOLOAD {
130 0     0     my ($self, $filename) = @_;
131             # get tail of method call
132 0           my ($method) = $Audio::Nama::File::AUTOLOAD =~ /([^:]+)$/;
133 0 0         croak "$method: illegal method call" unless $self->{$method};
134 0           my $dir_sub = $self->{$method}->[1];
135 0   0       $filename ||= $self->{$method}->[0];
136 0           my $path = Audio::Nama::join_path($dir_sub->(), $filename);
137 0           $path;
138             }
139       0     sub DESTROY {}
140 0           1;
141             }
142 0           $file = bless
143             {
144             effects_cache => ['.effects_cache.json', \&project_root],
145             gui_palette => ['palette', \&project_root],
146             state_store => ['State', \&project_dir ],
147             git_state_store => ['State.json', \&project_dir ],
148             untracked_state_store => ['Aux', \&project_dir ],
149             effect_profile => ['effect_profiles', \&project_root],
150             chain_setup => ['Setup.ecs', \&project_dir ],
151             user_customization => ['customize.pl', \&project_root],
152             project_effect_chains => ['project_effect_chains',\&project_dir ],
153             global_effect_chains => ['global_effect_chains', \&project_root],
154             old_effect_chains => ['effect_chains', \&project_root],
155             _logfile => ['nama.log', \&project_root],
156              
157              
158             }, 'Audio::Nama::File';
159              
160 0           $gui->{_save_id} = "State";
161 0           $gui->{_seek_unit} = 1;
162 0           $gui->{marks} = {};
163              
164              
165             #
166             # use this section to specify
167             # defaults for config variables
168             #
169             # These are initial, lowest priority defaults
170             # defaults for Nama config. Some variables
171             # may be overwritten during subsequent read_config's
172             #
173             # config variable sources are prioritized as follows
174              
175             #
176             # + command line argument -f /path/to/namarc
177             # + project specific namarc # currently disabled
178             # + user namarc (usually ~/.namarc)
179             # + internal namarc
180             # + internal initialization
181              
182              
183             $config = bless {
184             root_dir => join_path( $ENV{HOME}, "nama"),
185             soundcard_channels => 10,
186             memoize => 1,
187             use_pager => 1,
188             use_placeholders => 1,
189             use_git => 1,
190             autosave => 'undo',
191             volume_control_operator => 'ea', # default to linear scale
192             sync_mixdown_and_monitor_version_numbers => 1, # not implemented yet
193             engine_tcp_port => 2868, # 'default' engine
194             engine_fade_length_on_start_stop => 0.18,# when starting/stopping transport
195             engine_fade_default_length => 0.5, # for fade-in, fade-out
196             engine_base_jack_seek_delay => 0.1, # seconds
197             engine_command_output_buffer_size => 2**22, # 4 MB
198             edit_playback_end_margin => 3,
199             edit_crossfade_time => 0.03,
200             fade_down_fraction => 0.75,
201             fade_time1_fraction => 0.9,
202             fade_time2_fraction => 0.1,
203             fader_op => 'ea',
204             mute_level => {ea => 0, eadb => -96},
205             fade_out_level => {ea => 0, eadb => -40},
206             unity_level => {ea => 100, eadb => 0},
207             fade_resolution => 100, # steps per second
208             engine_muting_time => 0.03,
209             enforce_channel_bounds => 1,
210              
211             serialize_formats => 'json', # for save_system_state()
212              
213             latency_op => 'el:delay_n',
214             latency_op_init => [0,0],
215             latency_op_set => sub
216             {
217 0     0     my $id = shift;
218 0           my $delay = shift();
219 0           modify_effect($id,2,undef,$delay)
220             },
221 0           hotkey_beep => 'beep -f 250 -l 200',
222             # this causes beeping during make test
223             # beep_command => 'beep -f 350 -l 700',
224              
225             }, 'Audio::Nama::Config';
226              
227 0           { package Audio::Nama::Config;
228 1     1   13 use Carp;
  1         5  
  1         54  
229 1     1   5 use Audio::Nama::Globals qw(:singletons);
  1         2  
  1         200  
230 1     1   5 use Modern::Perl;
  1         3  
  1         5  
231 0           our @ISA = 'Audio::Nama::Object'; # for ->dump and ->as_hash methods
232              
233 0     0 0   sub serialize_formats { split " ", $_[0]->{serialize_formats} }
234              
235             sub hardware_latency {
236 1     1   148 no warnings 'uninitialized';
  1         3  
  1         438  
237 0 0   0 0   $config->{devices}->{$config->{alsa_capture_device}}{hardware_latency} || 0
238             }
239             sub buffersize {
240             package Audio::Nama;
241             Audio::Nama::ChainSetup::setup_requires_realtime()
242             ? $config->{engine_buffersize}->{realtime}->{default}
243             : $config->{engine_buffersize}->{nonrealtime}->{default}
244 0 0   0 0   }
245             sub globals_realtime {
246             Audio::Nama::ChainSetup::setup_requires_realtime()
247             ? $config->{engine_globals}->{realtime}
248             : $config->{engine_globals}->{nonrealtime}
249 0 0   0 0   }
250             } # end Audio::Nama::Config package
251              
252 0           $prompt = "nama ('h' for help)> ";
  0            
253              
254 0           $this_bus = 'Main';
255            
256 0           $setup->{_old_snapshot} = {};
257 0           $setup->{_last_rec_tracks} = [];
258              
259 0           $mastering->{track_names} = [ qw(Eq Low Mid High Boost) ];
260              
261 0 0         init_wav_memoize() if $config->{memoize};
262              
263             }
264              
265             sub initialize_interfaces {
266            
267             logsub("&intialize_interfaces");
268            
269             if ( ! $config->{opts}->{t} and Audio::Nama::Graphical::initialize_tk() ){
270             $ui = Audio::Nama::Graphical->new();
271             } else {
272             pager_newline( "Unable to load perl Tk module. Starting in console mode.") if $config->{opts}->{g};
273             $ui = Audio::Nama::Text->new();
274             can_load( modules =>{ Event => undef})
275             or die "Perl Module 'Event' not found. Please install it and try again. Stopping.";
276             ;
277             import Event qw(loop unloop unloop_all);
278             }
279            
280             can_load( modules => {AnyEvent => undef})
281             or die "Perl Module 'AnyEvent' not found. Please install it and try again. Stopping.";
282 1     1   773 use AnyEvent::TermKey qw( FORMAT_VIM KEYMOD_CTRL );
  0            
  0            
283             can_load( modules => {jacks => undef})
284             and $jack->{use_jacks}++;
285             choose_sleep_routine();
286             $config->{want_logging} = initialize_logger($config->{opts}->{L});
287              
288             $project->{name} = shift @ARGV;
289             {no warnings 'uninitialized';
290             logpkg(__FILE__,__LINE__,'debug',"project name: $project->{name}");
291             }
292              
293             logpkg(__FILE__,__LINE__,'debug', sub{"Command line options\n". json_out($config->{opts})});
294              
295             read_config(global_config()); # from .namarc if we have one
296              
297             logpkg(__FILE__,__LINE__,'debug',sub{"Config data\n".Dumper $config});
298            
299             select_ecasound_interface();
300            
301             start_osc_listener($config->{osc_listener_port})
302             if $config->{osc_listener_port}
303             and can_load(modules => {'Protocol::OSC' => undef});
304             start_remote_listener($config->{remote_control_port}) if $config->{remote_control_port};
305             logpkg(__FILE__,__LINE__,'debug',"reading config file");
306             if ($config->{opts}->{d}){
307             pager("project_root $config->{opts}->{d} specified on command line\n");
308             $config->{root_dir} = $config->{opts}->{d};
309             }
310             if ($config->{opts}->{p}){
311             $config->{root_dir} = getcwd();
312             pager("placing all files in current working directory ($config->{root_dir})\n");
313             }
314              
315             # skip initializations if user (test) supplies project
316             # directory
317            
318             first_run() unless $config->{opts}->{d};
319              
320             prepare_static_effects_data() unless $config->{opts}->{S};
321             setup_user_customization(); # depends on effect_index() in above
322              
323             get_ecasound_iam_keywords();
324             load_keywords(); # for autocompletion
325              
326             chdir $config->{root_dir} # for filename autocompletion
327             or warn "$config->{root_dir}: chdir failed: $!\n";
328              
329             $ui->init_gui;
330             $ui->transport_gui;
331             $ui->time_gui;
332            
333             # fake JACK for testing environment
334              
335             if( $config->{opts}->{J}){
336             parse_ports_list(get_data_section("fake_jack_lsp"));
337             parse_port_latency(get_data_section("fake_jack_latency"));
338             $jack->{jackd_running} = 1;
339             }
340              
341             # periodically check if JACK is running, and get client/port/latency list
342              
343             poll_jack() unless $config->{opts}->{J} or $config->{opts}->{A};
344              
345             sleeper(0.2); # allow time for first polling
346              
347             # we will start jack.plumbing only when we need it
348            
349             if( $config->{use_jack_plumbing}
350             and $jack->{jackd_running}
351             and process_is_running('jack.plumbing')
352             ){
353              
354             pager_newline(<
355             Jack.plumbing daemon detected!
356              
357             Attempting to stop it...
358              
359             (This may break other software that depends in jack.plumbing.)
360              
361             Nama will restart it as needed for Nama's use only.
362             PLUMB
363              
364             kill_jack_plumbing();
365             sleeper(0.2);
366             if( process_is_running('jack.plumbing') )
367             {
368             throw(q(Unable to stop jack.plumbing daemon.
369              
370             Please do one of the following, then restart Nama:
371              
372             - kill the jack.plumbing daemon ("killall jack.plumbing")
373             - set "use_jack_plumbing: 0" in .namarc
374              
375             ....Exiting.) );
376             exit;
377             }
378             else { pager_newline("Stopped.") }
379             }
380            
381             start_midish() if $config->{use_midish};
382              
383             initialize_terminal() unless $config->{opts}->{T};
384              
385             1;
386             }
387             { my $is_connected_remote;
388             sub start_remote_listener {
389             my $port = shift;
390             pager_newline("Starting remote control listener on port $port");
391             $project->{remote_control_socket} = IO::Socket::INET->new(
392             LocalAddr => 'localhost',
393             LocalPort => $port,
394             Proto => 'tcp',
395             Type => SOCK_STREAM,
396             Listen => 1,
397             Reuse => 1) || die $!;
398             start_remote_watcher();
399             }
400             sub start_remote_watcher {
401             $project->{events}->{remote_control} = AE::io(
402             $project->{remote_control_socket}, 0, \&process_remote_command )
403             }
404             sub remove_remote_watcher {
405             undef $project->{events}->{remote_control};
406             }
407             sub process_remote_command {
408             if ( ! $is_connected_remote++ ){
409             pager_newline("making connection");
410             $project->{remote_control_socket} =
411             $project->{remote_control_socket}->accept();
412             remove_remote_watcher();
413             $project->{events}->{remote_control} = AE::io(
414             $project->{remote_control_socket}, 0, \&process_remote_command );
415             }
416             my $input;
417             eval {
418             $project->{remote_control_socket}->recv($input, $project->{remote_control_socket}->sockopt(SO_RCVBUF));
419             };
420             $@ and throw("caught error: $@, resetting..."), reset_remote_control_socket(), revise_prompt(), return;
421             logpkg(__FILE__,__LINE__,'debug',"Got remote control socketput: $input");
422             process_command($input);
423             my $out;
424             { no warnings 'uninitialized';
425             $out = $text->{eval_result} . "\n";
426             }
427             eval {
428             $project->{remote_control_socket}->send($out);
429             };
430             $@ and throw("caught error: $@, resetting..."), reset_remote_control_socket(), revise_prompt(), return;
431             revise_prompt();
432             }
433             sub reset_remote_control_socket {
434             undef $is_connected_remote;
435             undef $@;
436             $project->{remote_control_socket}->shutdown(2);
437             undef $project->{remote_control_socket};
438             remove_remote_watcher();
439             start_remote_listener($config->{remote_control_port});
440             }
441             }
442              
443             sub start_osc_listener {
444             my $port = shift;
445             say("Starting OSC listener on port $port");
446             my $osc_in = $project->{osc_socket} = IO::Socket::INET->new(
447             LocalAddr => 'localhost',
448             LocalPort => $port,
449             Proto => 'udp',
450             Type => SOCK_DGRAM) || die $!;
451             $project->{events}->{osc} = AE::io( $osc_in, 0, \&process_osc_command );
452             $project->{osc} = Protocol::OSC->new;
453             }
454             sub process_osc_command {
455             my $in = $project->{osc_socket};
456             my $osc = $project->{osc};
457             my $source_ip = $in->recv(my $packet, $in->sockopt(SO_RCVBUF));
458             my($err, $hostname, $servicename) = getnameinfo($source_ip, NI_NUMERICHOST);
459             my $p = $osc->parse($packet);
460             my @args = @$p;
461             my ($path, $template, $command, @vals) = @args;
462             $path =~ s(^/)();
463             $path =~ s(/$)();
464             my ($trackname, $fx, $param) = split '/', $path;
465             process_command($trackname);
466             process_command("$command @vals") if $command;
467             process_command("show_effect $fx") if $fx; # select
468             process_command("show_track") if $trackname and not $fx;
469             process_command("show_tracks") if ! $trackname;
470             say "got OSC: ", Dumper $p;
471             say "got args: @args";
472             my $osc_out = IO::Socket::INET->new(
473             PeerAddr => $hostname,
474             PeerPort => $config->{osc_reply_port},
475             Proto => 'udp',
476             Type => SOCK_DGRAM) || die $!;
477             $osc_out->send(join "",@{$text->{output_buffer}});
478             delete $text->{output_buffer};
479             }
480              
481             sub sanitize_remote_input {
482             my $input = shift;
483             my $error_msg;
484             do{ $input = "" ; $error_msg = "error: perl/shell code is not allowed"}
485             if $input =~ /(^|;)\s*(!|eval\b)/;
486             throw($error_msg) if $error_msg;
487             $input
488             }
489             sub select_ecasound_interface {
490             my %args;
491             my $class;
492             if ($config->{opts}->{A} or $config->{opts}->{E})
493             {
494             pager_newline("Starting dummy engine only");
495             %args = (
496             name => 'Nama',
497             jack_transport_mode => 'send',
498             );
499             $class = 'Audio::Nama::Engine';
500             }
501             elsif (
502             $config->{opts}->{l}
503             and can_load( modules => { 'Audio::Ecasound' => undef })
504             and say("loaded Audio::Ecasound")
505             ){
506             %args = (
507             name => 'Nama',
508             jack_transport_mode => 'send',
509             );
510             $class = 'Audio::Nama::LibEngine';
511             }
512             else {
513             %args = (
514             name => 'Nama',
515             port => $config->{engine_tcp_port},
516             jack_transport_mode => 'send',
517             );
518             $class = 'Audio::Nama::NetEngine';
519             }
520             $class->new(%args);
521             }
522              
523              
524              
525             sub choose_sleep_routine {
526             if ( can_load(modules => {'Time::HiRes'=> undef} ) )
527             { *sleeper = *finesleep;
528             $config->{hires_timer}++; }
529             else { *sleeper = *select_sleep }
530             }
531             sub finesleep {
532             my $sec = shift;
533             Time::HiRes::usleep($sec * 1e6);
534             }
535             sub select_sleep {
536             my $seconds = shift;
537             select( undef, undef, undef, $seconds );
538             }
539             sub munge_category {
540            
541             my $cat = shift;
542            
543             # override undefined category by magical global setting
544             # default to 'ECI_OTHER'
545            
546             $cat ||= ($config->{category} || 'ECI_OTHER');
547              
548             # force all categories to 'ECI' if 'ECI' is selected for logging
549             # (exception: ECI_WAVINFO, which is too noisy)
550            
551             no warnings 'uninitialized';
552             return 'ECI' if $config->{want_logging}->{ECI} and not $cat eq 'ECI_WAVINFO';
553              
554             $cat
555             }
556              
557             sub start_logging {
558             $config->{want_logging} = initialize_logger($config->{opts}->{L})
559             }
560             sub eval_iam { $this_engine and $this_engine->eval_iam(@_) }
561             1;
562             __END__