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         131  
  1         3  
  1         55  
10 1     1   6 use Socket qw(getnameinfo NI_NUMERICHOST) ;
  1         2  
  1         709  
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   7 no warnings 'uninitialized';
  1         2  
  1         208  
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         530  
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   7 use Carp;
  1         2  
  1         62  
229 1     1   5 use Audio::Nama::Globals qw(:singletons);
  1         2  
  1         209  
230 1     1   5 use Modern::Perl;
  1         2  
  1         4  
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   150 no warnings 'uninitialized';
  1         2  
  1         445  
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   801 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             # set default project to "untitled"
386            
387             #convert_project_format(); # mark with .conversion_completed file in ~/nama
388            
389             if (! $project->{name} ){
390             $project->{name} = "untitled";
391             $config->{opts}->{c}++;
392             }
393             pager("\nproject_name: $project->{name}\n");
394            
395             load_project( name => $project->{name}, create => $config->{opts}->{c}) ;
396             1;
397             }
398             { my $is_connected_remote;
399             sub start_remote_listener {
400             my $port = shift;
401             pager_newline("Starting remote control listener on port $port");
402             $project->{remote_control_socket} = IO::Socket::INET->new(
403             LocalAddr => 'localhost',
404             LocalPort => $port,
405             Proto => 'tcp',
406             Type => SOCK_STREAM,
407             Listen => 1,
408             Reuse => 1) || die $!;
409             start_remote_watcher();
410             }
411             sub start_remote_watcher {
412             $project->{events}->{remote_control} = AE::io(
413             $project->{remote_control_socket}, 0, \&process_remote_command )
414             }
415             sub remove_remote_watcher {
416             undef $project->{events}->{remote_control};
417             }
418             sub process_remote_command {
419             if ( ! $is_connected_remote++ ){
420             pager_newline("making connection");
421             $project->{remote_control_socket} =
422             $project->{remote_control_socket}->accept();
423             remove_remote_watcher();
424             $project->{events}->{remote_control} = AE::io(
425             $project->{remote_control_socket}, 0, \&process_remote_command );
426             }
427             my $input;
428             eval {
429             $project->{remote_control_socket}->recv($input, $project->{remote_control_socket}->sockopt(SO_RCVBUF));
430             };
431             $@ and throw("caught error: $@, resetting..."), reset_remote_control_socket(), revise_prompt(), return;
432             logpkg(__FILE__,__LINE__,'debug',"Got remote control socketput: $input");
433             process_command($input);
434             my $out;
435             { no warnings 'uninitialized';
436             $out = $text->{eval_result} . "\n";
437             }
438             eval {
439             $project->{remote_control_socket}->send($out);
440             };
441             $@ and throw("caught error: $@, resetting..."), reset_remote_control_socket(), revise_prompt(), return;
442             revise_prompt();
443             }
444             sub reset_remote_control_socket {
445             undef $is_connected_remote;
446             undef $@;
447             $project->{remote_control_socket}->shutdown(2);
448             undef $project->{remote_control_socket};
449             remove_remote_watcher();
450             start_remote_listener($config->{remote_control_port});
451             }
452             }
453              
454             sub start_osc_listener {
455             my $port = shift;
456             say("Starting OSC listener on port $port");
457             my $osc_in = $project->{osc_socket} = IO::Socket::INET->new(
458             LocalAddr => 'localhost',
459             LocalPort => $port,
460             Proto => 'udp',
461             Type => SOCK_DGRAM) || die $!;
462             $project->{events}->{osc} = AE::io( $osc_in, 0, \&process_osc_command );
463             $project->{osc} = Protocol::OSC->new;
464             }
465             sub process_osc_command {
466             my $in = $project->{osc_socket};
467             my $osc = $project->{osc};
468             my $source_ip = $in->recv(my $packet, $in->sockopt(SO_RCVBUF));
469             my($err, $hostname, $servicename) = getnameinfo($source_ip, NI_NUMERICHOST);
470             my $p = $osc->parse($packet);
471             my @args = @$p;
472             my ($path, $template, $command, @vals) = @args;
473             $path =~ s(^/)();
474             $path =~ s(/$)();
475             my ($trackname, $fx, $param) = split '/', $path;
476             process_command($trackname);
477             process_command("$command @vals") if $command;
478             process_command("show_effect $fx") if $fx; # select
479             process_command("show_track") if $trackname and not $fx;
480             process_command("show_tracks") if ! $trackname;
481             say "got OSC: ", Dumper $p;
482             say "got args: @args";
483             my $osc_out = IO::Socket::INET->new(
484             PeerAddr => $hostname,
485             PeerPort => $config->{osc_reply_port},
486             Proto => 'udp',
487             Type => SOCK_DGRAM) || die $!;
488             $osc_out->send(join "",@{$text->{output_buffer}});
489             delete $text->{output_buffer};
490             }
491              
492             sub sanitize_remote_input {
493             my $input = shift;
494             my $error_msg;
495             do{ $input = "" ; $error_msg = "error: perl/shell code is not allowed"}
496             if $input =~ /(^|;)\s*(!|eval\b)/;
497             throw($error_msg) if $error_msg;
498             $input
499             }
500             sub select_ecasound_interface {
501             my %args;
502             my $class;
503             if ($config->{opts}->{A} or $config->{opts}->{E})
504             {
505             pager_newline("Starting dummy engine only");
506             %args = (
507             name => 'Nama',
508             jack_transport_mode => 'send',
509             );
510             $class = 'Audio::Nama::Engine';
511             }
512             elsif (
513             $config->{opts}->{l}
514             and can_load( modules => { 'Audio::Ecasound' => undef })
515             and say("loaded Audio::Ecasound")
516             ){
517             %args = (
518             name => 'Nama',
519             jack_transport_mode => 'send',
520             );
521             $class = 'Audio::Nama::LibEngine';
522             }
523             else {
524             %args = (
525             name => 'Nama',
526             port => $config->{engine_tcp_port},
527             jack_transport_mode => 'send',
528             );
529             $class = 'Audio::Nama::NetEngine';
530             }
531             $class->new(%args);
532             }
533              
534              
535              
536             sub choose_sleep_routine {
537             if ( can_load(modules => {'Time::HiRes'=> undef} ) )
538             { *sleeper = *finesleep;
539             $config->{hires_timer}++; }
540             else { *sleeper = *select_sleep }
541             }
542             sub finesleep {
543             my $sec = shift;
544             Time::HiRes::usleep($sec * 1e6);
545             }
546             sub select_sleep {
547             my $seconds = shift;
548             select( undef, undef, undef, $seconds );
549             }
550             sub munge_category {
551            
552             my $cat = shift;
553            
554             # override undefined category by magical global setting
555             # default to 'ECI_OTHER'
556            
557             $cat ||= ($config->{category} || 'ECI_OTHER');
558              
559             # force all categories to 'ECI' if 'ECI' is selected for logging
560             # (exception: ECI_WAVINFO, which is too noisy)
561            
562             no warnings 'uninitialized';
563             return 'ECI' if $config->{want_logging}->{ECI} and not $cat eq 'ECI_WAVINFO';
564              
565             $cat
566             }
567              
568             sub start_logging {
569             $config->{want_logging} = initialize_logger($config->{opts}->{L})
570             }
571             sub eval_iam { $this_engine and $this_engine->eval_iam(@_) }
572             1;
573             __END__