| 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__ |