# ----------- Engine Setup and Teardown -----------
package Audio::Nama;
use Modern::Perl;
use Audio::Nama::Log qw(logpkg);
no warnings 'uninitialized';
sub generate_setup {
# return 1 if successful
# catch errors from generate_setup_try() and cleanup
logsub("&generate_setup");
# extra argument (setup code) will be passed to generate_setup_try()
# my ($extra_setup_code) = @_;
# save current track
local $this_track;
# prevent engine from starting an old setup
eval_iam('cs-disconnect') if eval_iam('cs-connected');
Audio::Nama::ChainSetup::initialize();
# this is our chance to save state without the noise
# of temporary tracks, avoiding the issue of getting diffs
# in the project data from each new chain setup.
autosave() if $config->{autosave} eq 'setup'
and $project->{name}
and $config->{use_git}
and $project->{repo};
# TODO: use try/catch
# catch errors unless testing (no-terminal option)
local $@ unless $config->{opts}->{T};
track_memoize(); # freeze track state
my $success = $config->{opts}->{T} # don't catch errors during testing
? Audio::Nama::ChainSetup::generate_setup_try(@_)
: eval { Audio::Nama::ChainSetup::generate_setup_try(@_) };
track_unmemoize(); # unfreeze track state
if ($@){
throw("error caught while generating setup: $@");
Audio::Nama::ChainSetup::initialize();
return
}
$success;
}
{ my $old_offset_run_status;
sub reconfigure_engine {
logsub("&reconfigure_engine");
my $force = shift;
# skip if command line option is set
# don't skip if $force argument given
return if ($config->{opts}->{R} or $config->{disable_auto_reconfigure})
and not $force;
# don't disturb recording/mixing
return if Audio::Nama::ChainSetup::really_recording() and engine_running();
# store a lists of wav-recording tracks for the rerecord
# function
restart_wav_memoize(); # check if someone has snuck in some files
find_duplicate_inputs(); # we will warn the user later
if( $force or $setup->{changed} ){
logpkg(__FILE__,__LINE__,'debug',"reconfigure requested");
$setup->{_old_snapshot} = status_snapshot_string();
}
else {
my $old = $setup->{_old_snapshot};
my $current = $setup->{_old_snapshot} = status_snapshot_string();
if ( $current eq $old){
logpkg(__FILE__,__LINE__,'debug',"no change in setup");
return;
}
logpkg(__FILE__,__LINE__,'debug',"detected configuration change");
logpkg(__FILE__,__LINE__,'debug', diff(\$old, \$current));
}
$setup->{changed} = 0 ; # reset for next time
$old_offset_run_status = $mode->{offset_run};
process_command('show_tracks');
{ local $quiet = 1; stop_transport() }
trigger_rec_cleanup_hooks();
trigger_rec_setup_hooks();
$setup->{_old_rec_status} = {
map{$_->name => $_->rec_status } rec_hookable_tracks()
};
if ( generate_setup() ){
reset_latency_compensation() if $config->{opts}->{Q};
logpkg(__FILE__,__LINE__,'debug',"I generated a new setup");
{ local $quiet = 1; connect_transport() }
propagate_latency() if $config->{opts}->{Q} and $jack->{jackd_running};
show_status();
if ( Audio::Nama::ChainSetup::really_recording() )
{
$project->{playback_position} = 0
}
else
{
set_position($project->{playback_position}) if $project->{playback_position}
}
start_transport('quiet') if $mode->eager
and ($mode->doodle or $mode->preview);
transport_status();
$ui->flash_ready;
1
}
}
}
sub request_setup {
my ($package, $filename, $line) = caller();
logpkg(__FILE__,__LINE__,'debug',"reconfigure requested in file $filename:$line");
$setup->{changed}++
}
#### status_snapshot()
#
# hashref output for detecting if we need to reconfigure engine
# compared as YAML strings
# %status_snaphot indicates Nama's internal
# state. It consists of
# - the values of selected global variables
# - selected field values of each track
{
# these track fields will be inspected
my @relevant_track_fields = qw(
name
n
width
group
playat
region_start
region_end
looping
source_id
source_type
send_id
send_type
rec_status
current_version
);
sub status_snapshot {
my %snapshot = ( project => $project->{name},
mastering_mode => $mode->mastering,
preview => $mode->{preview},
jack_running => $jack->{jackd_running},
tracks => [], );
map { push @{$snapshot{tracks}}, $_->snapshot(\@relevant_track_fields) }
grep{ $_->rec_status ne OFF } grep { $_->group ne 'Temp' } Audio::Nama::all_tracks();
\%snapshot;
}
sub status_snapshot_string {
my $json = json_out(status_snapshot());
# hack to avoid false diff due to string/numerical
# representation
$json =~ s/: "(\d+)"/: $1/g;
$json
}
}
sub find_duplicate_inputs { # in Main bus only
%{$setup->{tracks_with_duplicate_inputs}} = ();
%{$setup->{inputs_used}} = ();
logsub("&find_duplicate_inputs");
map{ my $source = $_->source;
$setup->{tracks_with_duplicate_inputs}->{$_->name}++ if $setup->{inputs_used}->{$source} ;
$setup->{inputs_used}->{$source} //= $_->name;
}
grep { $_->rw eq REC }
map{ $tn{$_} }
$bn{Main}->tracks(); # track names;
}
sub load_ecs {
my $setup = shift;
#say "setup file: $setup " . ( -e $setup ? "exists" : "");
return unless -e $setup;
#say "passed conditional";
teardown_engine();
eval_iam("cs-load $setup");
eval_iam("cs-select $setup"); # needed by Audio::Ecasound, but not Net-ECI !!
logpkg(__FILE__,__LINE__,'debug',sub{map{eval_iam($_)} qw(cs es fs st ctrl-status)});
1;
}
sub teardown_engine {
eval_iam("cs-disconnect") if eval_iam("cs-connected");
eval_iam("cs-remove") if eval_iam("cs-selected");
}
sub arm {
logsub("&arm");
exit_preview_mode();
reconfigure_engine('force');
}
# substitute all live inputs by clock-sync'ed
# Ecasound null device 'rtnull'
sub arm_rtnull {
local %Audio::Nama::IO::io_class = qw(
null_in Audio::Nama::IO::from_null
null_out Audio::Nama::IO::to_null
soundcard_in Audio::Nama::IO::from_rtnull
soundcard_out Audio::Nama::IO::to_rtnull
wav_in Audio::Nama::IO::from_wav
wav_out Audio::Nama::IO::to_wav
loop_source Audio::Nama::IO::from_loop
loop_sink Audio::Nama::IO::to_loop
jack_manual_in Audio::Nama::IO::from_rtnull
jack_manual_out Audio::Nama::IO::to_rtnull
jack_ports_list_in Audio::Nama::IO::from_rtnull
jack_ports_list_out Audio::Nama::IO::to_rtnull
jack_multi_in Audio::Nama::IO::from_rtnull
jack_multi_out Audio::Nama::IO::to_rtnull
jack_client_in Audio::Nama::IO::from_rtnull
jack_client_out Audio::Nama::IO::to_rtnull
);
arm();
}
sub connect_transport {
logsub("&connect_transport");
remove_riff_header_stubs();
register_other_ports(); # that don't belong to my upcoming instance
load_ecs($file->chain_setup) or throw("No chain setup, engine not ready."), return;
valid_engine_setup()
or throw("Invalid chain setup, engine not ready."),return;
find_op_offsets();
eval_iam('cs-connect');
#or throw("Failed to connect setup, engine not ready"),return;
apply_ops();
apply_fades();
my $status = eval_iam("engine-status");
if ($status ne 'not started'){
throw("Invalid chain setup, cannot connect engine.\n");
return;
}
eval_iam('engine-launch');
$status = eval_iam("engine-status");
if ($status ne 'stopped'){
throw("Failed to launch engine. Engine status: $status\n");
return;
}
$setup->{audio_length} = eval_iam('cs-get-length'); # returns zero if unknown
sync_effect_parameters();
register_own_ports(); # as distinct from other Nama instances
$ui->length_display(-text => colonize($setup->{audio_length}));
eval_iam("cs-set-length $setup->{audio_length}") if $tn{Mixdown}->rec_status eq REC and $setup->{audio_length};
$ui->clock_config(-text => colonize(0));
sleeper(0.2); # time for ecasound engine to launch
# set delay for seeking under JACK
# we use a heuristic based on the number of tracks
# but it should be based on the number of PLAY tracks
my $track_count; map{ $track_count++ } Audio::Nama::ChainSetup::engine_tracks();
$jack->{seek_delay} = $jack->{jackd_running}
? $config->{engine_base_jack_seek_delay} * ( 1 + $track_count / 20 )
: 0;
connect_jack_ports_list();
transport_status() unless $quiet;
$ui->flash_ready();
#print eval_iam("fs");
1;
}
sub transport_status {
map{
pager("Warning: $_: input ",$tn{$_}->source,
" is already used by track ",$setup->{inputs_used}->{$tn{$_}->source},".")
if $setup->{tracks_with_duplicate_inputs}->{$_};
} grep { $tn{$_}->rec_status eq REC } $bn{Main}->tracks;
# assume transport is stopped
# print looping status, setup length, current position
my $start = Audio::Nama::Mark::loop_start();
my $end = Audio::Nama::Mark::loop_end();
#print "start: $start, end: $end, loop_enable: $mode->{loop_enable}\n";
if (ref $setup->{cooked_record_pending} and %{$setup->{cooked_record_pending}}){
pager(join(" ", keys %{$setup->{cooked_record_pending}}), ": ready for caching");
}
if ($mode->{loop_enable} and $start and $end){
#if (! $end){ $end = $start; $start = 0}
pager("looping from ", heuristic_time($start),
"to ", heuristic_time($end));
}
pager("\nNow at: ", current_position());
pager("Engine is ". ( engine_running() ? "running." : "ready."));
pager("\nPress SPACE to start or stop engine.\n")
if $config->{press_space_to_start};
}
sub trigger_rec_setup_hooks {
map { system($_->rec_setup_script) }
grep
{
logpkg(__FILE__,__LINE__,'trace',
join "\n",
"track ".$_->name,
"rec status is: ".$_->rec_status,
"old rec status: ".$setup->{_old_rec_status}->{$_->name},
"script was ". (-e $_->rec_setup_script ) ? "found" : "not found"
);
$_->rec_status eq REC
and $setup->{_old_rec_status}->{$_->name} ne REC
and -e $_->rec_setup_script
}
rec_hookable_tracks();
}
sub trigger_rec_cleanup_hooks {
map { system($_->rec_cleanup_script) }
grep
{ $_->rec_status ne REC
and $setup->{_old_rec_status}->{$_->name} eq REC
and -e $_->rec_cleanup_script
}
rec_hookable_tracks();
}
1;
__END__