view release on metacpan or search on metacpan
lib/Audio/Nama.pm view on Meta::CPAN
map{ my $pid = $_;
map{ my $signal = $_;
kill $signal, $pid;
sleeper(0.2);
} (15,9);
waitpid $pid, 1;
} @pids;
}
sub cleanup_exit {
logsub((caller(0))[3]);
remove_riff_header_stubs();
trigger_rec_cleanup_hooks();
# for each process:
# - SIGINT (1st time)
# - allow time to close down
# - SIGINT (2nd time)
# - allow time to close down
# - SIGKILL
#project_snapshot();
Audio::Nama::Engine::sync_action('kill_and_reap');
lib/Audio/Nama/Assign.pm view on Meta::CPAN
sub assign {
# Usage:
# assign (
# data => $ref,
# vars => \@vars,
# var_map => 1,
# class => $class
# );
logsub((caller(0))[3]);
my %h = @_; # parameters appear in %h
my $class;
logpkg(__FILE__,__LINE__,'logcarp',"didn't expect scalar here") if ref $h{data} eq 'SCALAR';
logpkg(__FILE__,__LINE__,'logcarp',"didn't expect code here") if ref $h{data} eq 'CODE';
# print "data: $h{data}, ", ref $h{data}, $/;
if ( ref $h{data} !~ /^(HASH|ARRAY|CODE|GLOB|HANDLE|FORMAT)$/){
# we guess object
$class = ref $h{data};
lib/Audio/Nama/Assign.pm view on Meta::CPAN
$fx_cache
$text
$gui
$midi
$help
$mastering
$project
);
sub assign_singletons {
logsub((caller(0))[3]);
my $ref = shift;
my $data = $ref->{data} or die "expected data got undefined";
my $class = $ref->{class} // 'Audio::Nama';
$class .= '::'; # SKIP_PREPROC
map {
my $ident = $_;
if( defined $data->{$ident}){
my $type = ref $data->{$ident};
$type eq 'HASH' or die "$ident: expect hash, got $type";
map{
lib/Audio/Nama/Assign.pm view on Meta::CPAN
{
my $parse_re = # initialize only once
qr/ ^ # beginning anchor
([\%\@\$]) # first character, sigil
([\w:]+) # identifier, possibly perl namespace
(?:->\{(\w+)})? # optional hash key for new hash-singleton vars
$ # end anchor
/x;
sub serialize {
logsub((caller(0))[3]);
my %h = @_;
my @vars = @{ $h{vars} };
my $class = $h{class};
my $file = $h{file};
my $format = $h{format} // 'perl'; # default to Data::Dumper::Concise
$class //= "Audio::Nama";
$class =~ /::$/ or $class .= '::'; # SKIP_PREPROC
logpkg(__FILE__,__LINE__,'debug',"file: $file, class: $class\nvariables...@vars");
lib/Audio/Nama/Assign.pm view on Meta::CPAN
# now we serialize %state
my $path = $h{file};
serialize_and_write(\%state, $path, $format);
}
}
sub json_out {
logsub((caller(0))[3]);
my $data_ref = shift;
my $type = ref $data_ref;
croak "attempting to code wrong data type: $type"
if $type !~ /HASH|ARRAY/;
$to_json->encode($data_ref);
}
sub json_in {
logsub((caller(0))[3]);
my $json = shift;
my $data_ref = decode_json($json);
$data_ref
}
sub yaml_in {
# logsub((caller(0))[3]);
my $input = shift;
my $yaml = $input =~ /\n/ # check whether file or text
? $input # yaml text
: do
{
logpkg(__FILE__,__LINE__,'debug',"filename: $input");
read_file($input); # file name
};
if ($yaml =~ /\t/){
croak "YAML file: $input contains illegal TAB character.";
lib/Audio/Nama/CacheTrack.pm view on Meta::CPAN
# track
# additional_time
# processing_time
# original_version
# output_wav
# orig_volume
# orig_pan
# bus - we are caching a bus
sub cache_track { # launch subparts if conditions are met
logsub((caller(0))[3]);
my $args = {}; # to pass params to routines involved in caching
(my $track, $args->{additional_time}) = @_;
my $bus = $track->is_mixing;
my $obj; # track or bus
my $name = $track->name;
if( $track->off ){
my $bus = $track->is_mixer && ! $track->playback_version;
my $status = $bus ? MON : PLAY;
throw(qq(Cannot cache track "$name" with status OFF. Set to $status and try again));
lib/Audio/Nama/CacheTrack.pm view on Meta::CPAN
# set WAV output format
$g->set_vertex_attributes(
$track->name,
{ format => signal_format($config->{cache_to_disk_format},$track->width),
version => ($args->{track_result_version}),
}
);
}
sub generate_cache_track_graph {
logsub((caller(0))[3]);
my $args = shift;
my $g = Audio::Nama::ChainSetup::initialize();
$args->{graph} = $g;
# We route the signal thusly:
#
# Target track --> CacheRecTrack --> wav_out
#
# CacheRecTrack slaves to target target
# - same name
lib/Audio/Nama/CacheTrack.pm view on Meta::CPAN
my $from_path = join_path($args->{track}->dir, $from_name);
$g->set_vertex_attributes(
$args->{track}->name,
{ full_path => $from_path }
);
}
sub process_cache_graph {
logsub((caller(0))[3]);
my $g = shift;
logpkg(__FILE__,__LINE__,'debug', "The graph after bus routing:\n$g");
Audio::Nama::ChainSetup::prune_graph();
logpkg(__FILE__,__LINE__,'debug', "The graph after pruning:\n$g");
Audio::Nama::Graph::expand_graph($g);
logpkg(__FILE__,__LINE__,'debug', "The graph after adding loop devices:\n$g");
Audio::Nama::Graph::add_inserts($g);
logpkg(__FILE__,__LINE__,'debug', "The graph with inserts:\n$g");
my $success = Audio::Nama::ChainSetup::process_routing_graph();
if ($success)
{
Audio::Nama::ChainSetup::write_chains();
Audio::Nama::ChainSetup::remove_temporary_tracks();
}
$success
}
sub cache_engine_run {
logsub((caller(0))[3]);
my $args = shift;
connect_transport()
or throw("Couldn't connect engine! Aborting."), return;
$args->{processing_time} = $setup->{audio_length} + $args->{additional_time};
pager($args->{track}->name.": processing time: ". d2($args->{processing_time}). " seconds");
pager("Starting cache operation. Please wait.");
revise_prompt(" ");
lib/Audio/Nama/CacheTrack.pm view on Meta::CPAN
# we try to set processing time this way
ecasound_iam("cs-set-length $args->{processing_time}");
ecasound_iam("start");
# ensure that engine stops at completion time
$setup->{cache_track_args} = $args;
start_event(poll_engine => timer(1, 0.5, \&poll_progress));
}
sub complete_caching {
logsub((caller(0))[3]);
my $args = shift;
my $name = $args->{track}->name;
my @files = grep{/$name/} new_files_were_recorded();
if (@files ){
update_cache_map($args);
caching_cleanup($args);
} else { throw("track cache operation failed!") }
undef $setup->{cache_track_args};
}
sub update_cache_map {
logsub((caller(0))[3]);
my $args = shift;
logpkg(__FILE__,__LINE__,'debug', "updating track cache_map");
logpkg(__FILE__,__LINE__,'debug', "current track cache entries:",
sub {
join "\n","cache map",
map{($_->dump)} Audio::Nama::EffectChain::find(track_cache => 1)
});
my $track = $args->{track};
lib/Audio/Nama/ChainSetup.pm view on Meta::CPAN
@input_chains, # list of input chain segments
@output_chains, # list of output chain segments
@post_input, # post-input chain operators
@pre_output, # pre-output chain operators
$chain_setup, # final result as string
);
sub remove_temporary_tracks {
logsub((caller(0))[3]);
map { logpkg(__FILE__,__LINE__,'debug',"removing temporary track ",$_->group.'/'.$_->name); $_->remove }
grep{ $_->group eq 'Temp' } Audio::Nama::audio_tracks();
}
sub initialize {
remove_temporary_tracks(); # we will generate them again
$setup->{audio_length} = 0;
@io = (); # IO object list
Audio::Nama::IO::initialize();
$g = Graph->new();
lib/Audio/Nama/ChainSetup.pm view on Meta::CPAN
sub warn_missing_jack_clients {
for my $track (Audio::Nama::audio_tracks()){
$track->send_type =~ /jack_client/ and not $jack->{clients}->{$track->send_id}
and Audio::Nama::throw("Track ".$track->name. qq(: JACK client ").$track->send_id.qq(" not found. Skipping aux send));
$track->source_type eq 'jack_client' and not $jack->{clients}->{$track->source_id}
and Audio::Nama::throw("Track ".$track->name. qq(: JACK client ").$track->source_id.qq(" not found. Skipping source connection));
}
}
sub generate_setup_try {
logsub((caller(0))[3]);
my $extra_setup_code = shift;
warn_missing_jack_clients();
# start with bus routing
map{ $_->apply($g) } Audio::Nama::Bus::all();
logpkg(__FILE__,__LINE__,'debug',"Graph after bus routing:\n$g");
lib/Audio/Nama/ChainSetup.pm view on Meta::CPAN
}
}
sub add_paths_for_aux_sends { # not including Main
# currently this routing is track-oriented
# we could add this to the Audio::Nama::Bus base class
# then suppress it in Mixdown and Main groups
logsub((caller(0))[3]);
map { Audio::Nama::Graph::add_path_for_aux_send($g, $_ ) }
grep { $_->group !~ /Mixdown|Null/
and $_->send_type
and $_->rec_status ne OFF } Audio::Nama::audio_tracks();
}
sub add_paths_from_Main {
logsub((caller(0))[3]);
if ($mode->mastering){
$g->add_path(qw[Main Eq Low Boost]);
$g->add_path(qw[Eq Mid Boost]);
$g->add_path(qw[Eq High Boost]);
}
else {
$g->add_path('Main', output_node($tn{Main}->send_type)) if $tn{Main}->mon
and ! $tn{Mixdown}->rec
# tests require this, why not generated by
# add_paths_for_aux_sends() ??
}
}
sub add_paths_for_mixdown_handling {
logsub((caller(0))[3]);
my $final_leg_origin = $mode->mastering ? 'Boost' : 'Main';
if ($tn{Mixdown}->rw eq REC ){
# don't monitor via soundcard
$g->delete_edge('Main','soundcard_out');
$g->delete_edge('Boost','soundcard_out');
my @p = ($final_leg_origin, ,'Mixdown', 'wav_out');
$g->add_path(@p);
$g->set_vertex_attributes('Mixdown', {
format_template => $config->{mix_to_disk_format},
lib/Audio/Nama/ChainSetup.pm view on Meta::CPAN
my @e = ('wav_in','Mixdown',output_node($tn{Main}->send_type));
$g->add_path(@e);
$g->set_vertex_attributes('Mixdown', {
send_type => $tn{Main}->send_type,
send_id => $tn{Main}->send_id,
chain => "Mixdown" });
# no effects will be applied because effects are on chain 2
}
}
sub prune_graph {
logsub((caller(0))[3]);
Audio::Nama::Graph::simplify_send_routing($g);
logpkg(__FILE__,__LINE__,'debug',"Graph after simplify_send_routing:\n$g");
Audio::Nama::Graph::remove_out_of_bounds_tracks($g) if Audio::Nama::edit_mode();
logpkg(__FILE__,__LINE__,'debug',"Graph after remove_out_of_bounds_tracks:\n$g");
Audio::Nama::Graph::recursively_remove_inputless_tracks($g);
logpkg(__FILE__,__LINE__,'debug',"Graph after recursively_remove_inputless_tracks:\n$g");
Audio::Nama::Graph::recursively_remove_outputless_tracks($g);
logpkg(__FILE__,__LINE__,'debug',"Graph after recursively_remove_outputless_tracks:\n$g");
}
# object based dispatch from routing graph
sub process_routing_graph {
logsub((caller(0))[3]);
# generate a set of IO objects from edges
@io = map{ dispatch($_) } $g->edges;
logpkg(__FILE__,__LINE__,'debug', sub{ join "\n",map $_->dump, @io });
# sort chain_ids by attached input object
# one line will show all with that one input
# -a:3,5,6 -i:foo
lib/Audio/Nama/ChainSetup.pm view on Meta::CPAN
my ($a, $b) = @{$_[0]};
#say "a: $a, b: $b";
my ($name, $endpoint) = $tn{$a} ? @{$_[0]} : reverse @{$_[0]} ;
my $direction = $tn{$a} ? 'output' : 'input';
($name, $endpoint, $direction)
}
sub override {
# data from edges has priority over data from vertexes
# we specify $name, because it could be left or right
# vertex
logsub((caller(0))[3]);
my ($name, $edge) = @_;
(override_from_vertex($name), override_from_edge($edge))
}
sub override_from_vertex {
my $name = shift;
warn("undefined graph\n"), return () unless (ref $g) =~ /Graph/;
my $attr = $g->get_vertex_attributes($name);
$attr ? %$attr : ();
}
sub override_from_edge {
my $edge = shift;
warn("undefined graph\n"), return () unless (ref $g) =~ /Graph/;
my $attr = $g->get_edge_attributes(@$edge);
$attr ? %$attr : ();
}
sub write_chains {
logsub((caller(0))[3]);
## write general options
my @globals;
my $format = signal_format($config->{devices}->{jack}->{signal_format},2); # HARDCODED XXX
push @globals, $config->{ecasound_globals}->{common};
push @globals, "-f:$format", join(',', '-G:jack',
$config->{ecasound_jack_client_name},
$config->{jack_transport_mode}
) if $jack->{jackd_running};
lib/Audio/Nama/Config.pm view on Meta::CPAN
# sub global_config {
# read_file( join_path($ENV{HOME}, config_file()));
# }
sub read_config {
# read and process the configuration file
#
# use the embedded default file if none other is present
logsub((caller(0))[3]);
my $config_file = shift;
my $yml = $config_file // get_data_section("default_namarc");
strip_all( $yml );
my %cfg = %{ yaml_in($yml) };
logpkg(__FILE__,__LINE__,'debug', "config file:", Dumper \%cfg);
*subst = \%{$cfg{abbreviations}}; # alias
walk_tree(\%cfg);
walk_tree(\%cfg); # second pass completes substitutions
lib/Audio/Nama/Config.pm view on Meta::CPAN
loads initial_mix.json");
$config->{use_git} = $config->{use_git} && git_executable_found() ? 1 : 0;
}
sub git_executable_found { qx(which git) }
sub walk_tree {
#logsub((caller(0))[3]);
my $ref = shift;
map { substitute($ref, $_) }
grep {$_ ne q(abbreviations)}
keys %{ $ref };
}
sub substitute{
my ($parent, $key) = @_;
my $val = $parent->{$key};
#logpkg(__FILE__,__LINE__,'debug', qq(key: $key val: $val\n) );
ref $val and walk_tree($val)
lib/Audio/Nama/EcasoundCleanup.pm view on Meta::CPAN
}
package Audio::Nama;
use v5.36;
use Cwd;
use File::Spec::Functions qw(splitpath);
use Audio::Nama::Globals qw(:all);
sub rec_cleanup {
logsub((caller(0))[3]);
logpkg(__FILE__,__LINE__,'debug',"transport still running, can't cleanup"), return if $this_engine->running;
if( my (@files) = new_files_were_recorded() )
{
if( my @rec_tracks = Audio::Nama::ChainSetup::engine_wav_out_tracks() )
{
$project->{playback_position} = 0;
$setup->{_last_rec_tracks} = \@rec_tracks;
}
if( grep /Mixdown/, @files) {
mixdown_postprocessing() ;
mixplay();
}
post_rec_configure()
}
}
sub mixdown_postprocessing {
logsub((caller(0))[3]);
nama_cmd('mixplay');
my ($oldfile) = $tn{Mixdown}->full_path =~ m{([^/]+)$};
$oldfile = join_path('.wav',$oldfile);
my $tag_name = join '-', $project->{name}, current_branch();
my $version = $tn{Mixdown}->playback_version;
# simplify the tagname basename
#
# untitled-master -> untitled
# untitled-premix-branch -> untitled-premix
lib/Audio/Nama/EcasoundCleanup.pm view on Meta::CPAN
$comment .= "encoded " if $encoding;
$comment .= "as $tag_name ";
$comment .= "(commit $sha)" if $sha;
}
$tn{Mixdown}->add_system_version_comment($version, $comment);
pager_newline($comment);
encode_mixdown_file($oldfile,$tag_name);
chdir $was_in;
}
sub tag_mixdown_commit {
logsub((caller(0))[3]);
my ($name, $newfile, $mixdownfile) = @_;
logpkg(__FILE__,__LINE__,'debug',"tag_mixdown_commit: @_");
my ($sym) = $newfile =~ m([^/]+$);
my ($mix) = $mixdownfile =~ m([^/]+$);
# we want to tag the normal playback state
local $quiet = 1;
mixoff();
my $msg = "State for $sym ($mix)";
project_snapshot($msg);
git('tag', $name, '-m', $mix);
}
sub delete_existing_mixdown_tag_and_convenience_encodings {
logsub((caller(0))[3]);
my $name = shift;
logpkg(__FILE__,__LINE__,'debug',"name: $name");
git('tag', '-d', $name);
foreach( qw(mp3 ogg wav) ){
my $file = join_path(project_dir(),"$name.$_");
unlink $file if -e $file;
}
}
sub encode_mixdown_file {
state $shell_encode_command = {
lib/Audio/Nama/EcasoundSetup.pm view on Meta::CPAN
use v5.36;
our $VERSION = 1.0;
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg logsub);
sub setup {
package Audio::Nama;
no warnings 'uninitialized';
my $self = shift;
# return 1 if successful
# catch errors from generate_setup_try() and cleanup
logsub((caller(0))[3]);
# 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
ecasound_iam('cs-disconnect') if ecasound_iam('cs-connected');
lib/Audio/Nama/EcasoundSetup.pm view on Meta::CPAN
### legacy ecasound support routines in root namespace
package Audio::Nama;
use v5.36;
no warnings 'uninitialized';
sub find_duplicate_inputs { # in Main bus only
%{$setup->{tracks_with_duplicate_inputs}} = ();
%{$setup->{inputs_used}} = ();
logsub((caller(0))[3]);
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;
lib/Audio/Nama/EcasoundSetup.pm view on Meta::CPAN
$setup eq $result or throw("$result: failed to select chain setup");
logpkg(__FILE__,__LINE__,'debug',sub{map{ecasound_iam($_)} qw(cs es fs st ctrl-status)});
1;
}
sub teardown_engine {
ecasound_iam("cs-disconnect") if ecasound_iam("cs-connected");
ecasound_iam("cs-remove") if ecasound_iam("cs-selected");
}
sub arm {
logsub((caller(0))[3]);
exit_preview_modes();
request_setup();
reconfigure_engine();
}
# substitute all live inputs by clock-sync'ed
# Ecasound null device 'rtnull'
sub arm_rtnull {
lib/Audio/Nama/EcasoundSetup.pm view on Meta::CPAN
);
arm();
}
sub something_to_run { $en{ecasound}->valid_setup or midi_run_ready() }
sub midi_run_ready { $config->{use_midi} and $en{midish} and $en{midish}->is_active }
sub connect_transport {
logsub((caller(0))[3]);
remove_riff_header_stubs();
register_other_ports(); # we won't see Nama ports since Nama hasn't started
load_ecs($file->chain_setup) or Audio::Nama::throw("failed to load chain setup"), return;
$this_engine->valid_setup()
or throw("Invalid chain setup, engine not ready."),return;
find_op_offsets();
setup_fades();
apply_ops();
ecasound_iam('cs-connect');
#or throw("Failed to connect setup, engine not ready"),return;
lib/Audio/Nama/Effect.pm view on Meta::CPAN
{
return 1 if $entry->{params}->[$_]->{dir} eq 'output'
}
}
sub registry_index {
my $self = shift;
$fx_cache->{full_label_to_index}->{ $self->type };
}
sub ecasound_controller_index {
logsub((caller(0))[3]);
my $self = shift;
my $n = $self->chain;
my $id = $self->id;
my $opcount = 0;
logpkg(__FILE__,__LINE__,'debug', "id: $id, n: $n, ops: @{ $ti{$n}->ops }" );
for my $op (@{ $ti{$n}->ops }) {
# increment only controllers
next unless fxn($op)->is_controller;
$opcount++;
last if $op eq $id;
}
$opcount;
}
sub ecasound_effect_index {
logsub((caller(0))[3]);
my $self = shift;
my $n = $self->chain;
my $id = $self->id;
my $opcount = 0;
logpkg(__FILE__,__LINE__,'debug', "id: $id, n: $n, ops: @{ $ti{$n}->ops }" );
for my $op (@{ $ti{$n}->ops }) {
my $fx = fxn($op);
next if $fx->is_controller;
++$opcount; # first index is 1
last if $op eq $id
lib/Audio/Nama/Effect.pm view on Meta::CPAN
}
else { $new_value = $value }
logpkg(__FILE__,__LINE__,'debug', "id $op_id p: $parameter, sign: $sign value: $value");
update_effect(
$op_id,
$parameter,
$new_value);
1
}
sub _remove_effect {
logsub((caller(0))[3]);
my $self = shift;
my $id = $self->id;
my $n = $self->chain;
my $parent = $self->parent;
my $owns = $self->owns;
logpkg(__FILE__,__LINE__,'debug', "id: $id", ($parent ? ". parent: ".$parent->id : '' ));
my $object = $parent ? q(controller) : q(chain operator);
logpkg(__FILE__,__LINE__,'debug', qq(ready to remove $object "$id" from track "$n"));
lib/Audio/Nama/Effect.pm view on Meta::CPAN
my @ops_list = @{$track->ops};
my @new_list = grep { $_ ne $id } @ops_list;
$track->{ops} = [ @new_list ];
}
#set_current_op($this_track->ops->[0]);
#set_current_param(1);
delete $by_id{$self->id};
return();
}
sub position_effect {
#logsub((caller(0))[3]);
my($self, $pos) = @_;
my $op = $self->id;
#Audio::Nama::pager("$op or $pos: controller not allowed, skipping.\n"), return
# if grep{ fxn($_)->is_controller } $op, $pos;
# first, modify track data structure
my $track = $ti{$self->chain};
lib/Audio/Nama/Effect.pm view on Meta::CPAN
# easier to reconfigure the engine than to code for
# repositioning ecasound effects.
say join " - ",@new_op_list;
@{$track->ops} = @new_op_list;
Audio::Nama::request_setup();
$this_track = $track;
nama_cmd('show_track');
}
sub apply_op {
logsub((caller(0))[3]);
my $self = shift;
local $config->{category} = 'ECI_FX';
my $id = $self->id;
logpkg(__FILE__,__LINE__,'debug', "id: $id");
logpkg(__FILE__,__LINE__,'logcluck', "$id: expected effect entry not found!"), return
if effect_entry_is_bad($id);
my $code = $self->type;
my $dad = $self->parent;
my $chain = $self->chain;
logpkg(__FILE__,__LINE__,'debug', "chain: $chain, type: $code");
lib/Audio/Nama/Effect.pm view on Meta::CPAN
# user command: add_effect <effect_chain_name>
# add_effect(effect_chain => $fxc) calls insert_effect()
# insert_effect()
# * removes preceding operators
# * calls append_effect(effect_chain => $fxc)
# + which calls $fxc->add
# + which calls append_effect() for each effect
# * restores the operators
sub add_effect {
#logsub((caller(0))[3]);
my $args = shift;
my $added = _add_effect($args);
$added->[0]->id
}
sub _add_effect {
my $p = shift;
logsub((caller(0))[3]);
#logpkg(__FILE__,__LINE__,'debug',sub{ "add effect arguments - 0:\n".json_out($p)});
set_chain_value($p);
### We prohibit creating effects on the Mixdown track
### We check $track->forbid_user_ops
### which is set on the Mixdown track,
### An alternative would be giving each
lib/Audio/Nama/Effect.pm view on Meta::CPAN
if ($running){
ecasound_iam('start');
sleeper(0.3);
Audio::Nama::unmute();
$ui->start_heartbeat;
}
$added;
}
sub modify_effect {
logsub((caller(0))[3]);
my ($op_id, $parameter, $sign, $value) = @_;
# $parameter: one-based
my $FX = fxn($op_id)
or pager("$op_id: non-existing effect id. Skipping.\n"), return;
$FX->_modify_effect($parameter, $value, $sign);
}
sub modify_multiple_effects {
logsub((caller(0))[3]);
my ($op_ids, $parameters, $sign, $value) = @_;
map{ my $op_id = $_;
map{ my $parameter = $_;
modify_effect($op_id, $parameter, $sign, $value);
set_current_op($op_id);
set_current_param($parameter);
} @$parameters;
} @$op_ids;
}
sub remove_effect {
logsub((caller(0))[3]);
my $id = shift;
my $FX = fxn($id)
or logpkg(__FILE__,__LINE__,'logcarp',"$id: does not exist, skipping...\n"), return;
$FX->_remove_effect;
}
sub full_effect_code {
# get text effect code from user input, which could be
# - LADSPA Unique ID (number)
# - LADSPA Label (el:something)
lib/Audio/Nama/Effect.pm view on Meta::CPAN
return [] unless defined $p->{default};
push @$values, $p->{default};
}
$values
}
## Ecasound engine -- apply/remove chain operators
sub apply_ops { # in addition to operators in .ecs file
logsub((caller(0))[3]);
for my $track ( Audio::Nama::audio_tracks() ) {
my $n = $track->n;
next unless Audio::Nama::ChainSetup::is_ecasound_chain($n);
logpkg(__FILE__,__LINE__,'debug', "chain: $n, offset: $fx->{offset}->{$n}");
$this_engine->reset_ecasound_selections_cache();
$this_engine->current_chain($n);
$track->apply_ops;
}
}
sub remove_op {
# remove chain operator from Ecasound engine
logsub((caller(0))[3]);
local $config->{category} = 'ECI_FX';
# only if engine is configured
return unless $this_engine->valid_setup();
my $id = shift;
my $self = fxn($id);
Audio::Nama::request_setup(), return if $self->is_channel_op;
my $n = $self->chain;
lib/Audio/Nama/Effect.pm view on Meta::CPAN
## synchronize Ecasound chain operator parameters
# with Nama effect parameter
sub update_ecasound_effect {
local $config->{category} = 'ECI_FX';
# update the parameters of the Ecasound chain operator
# referred to by a Nama operator_id
#logsub((caller(0))[3]);
return unless $this_engine->valid_setup;
#my $es = ecasound_iam("engine-status");
#logpkg(__FILE__,__LINE__,'debug', "engine is $es");
#return if $es !~ /not started|stopped|running/;
my ($id, $param, $val) = @_;
my $FX = fxn($id) or carp("$id: effect not found. skipping...\n"), return;
$param++; # so the value at $p[0] is applied to parameter 1
lib/Audio/Nama/Effect.pm view on Meta::CPAN
# parameters
sub update_effect {
my ($id, $param, $val) = @_;
return if ! defined fxn($id);
fxn($id)->params->[$param] = $val;
update_ecasound_effect( @_ );
}
sub sync_effect_parameters {
logsub((caller(0))[3]);
local $config->{category} = 'ECI_FX';
# when a controller changes an effect parameter, the
# parameter value can differ from Nama's value for that
# parameter.
#
# this routine syncs them in prep for save_state()
return unless $this_engine->valid_setup();
push my @ops, ops_with_controller(), ops_with_read_only_params();
lib/Audio/Nama/Effect.pm view on Meta::CPAN
grep{ $_->has_read_only_param() }
map{ fxn($_) }
map{ @{ $_->ops } }
Audio::Nama::ChainSetup::engine_tracks();
}
sub find_op_offsets {
local $config->{category} = 'ECI_FX';
logsub((caller(0))[3]);
my @op_offsets = grep{ /"\d+"/} split "\n",ecasound_iam("cs");
logpkg(__FILE__,__LINE__,'debug', join "\n\n",@op_offsets);
for my $output (@op_offsets){
my $chain_id;
($chain_id) = $output =~ m/Chain "(\w*\d+)"/;
# "print chain_id: $chain_id\n";
next if $chain_id =~ m/\D/; # skip id's containing non-digits
# i.e. M1
my $quotes = $output =~ tr/"//;
logpkg(__FILE__,__LINE__,'debug', "offset: $quotes in $output");
lib/Audio/Nama/Effect.pm view on Meta::CPAN
}
sub restore_effects {
my($track, @ops) = @_;
set_bypass_state($track, 'off', @ops);
}
sub set_bypass_state {
local $config->{category} = 'ECI_FX';
my($track, $bypass_state, @ops) = @_;
logsub((caller(0))[3]);
# only process ops that belong to this track
@ops = intersect_with_track_ops_list($track,@ops);
$this_engine->current_chain($track->n);
$track->mute;
foreach my $op ( @ops)
{
my $FX = fxn($op);
lib/Audio/Nama/EffectChain.pm view on Meta::CPAN
my ($ec) = Audio::Nama::EffectChain::find(
unique => 1,
user => 1,
name => $name,
);
if( $ec ){ $ec->add($Audio::Nama::this_track) }
else { Audio::Nama::throw("$name: effect chain not found") }
1;
}
sub new_effect_profile {
logsub((caller(0))[3]);
my ($bunch, $profile) = @_;
my @tracks = bunch_tracks($bunch);
Audio::Nama::pager( qq(effect profile "$profile" created for tracks: @tracks) );
map {
Audio::Nama::EffectChain->new(
profile => $profile,
user => 1,
global => 1,
track_name => $_,
ops_list => [ $tn{$_}->user_ops ],
inserts_data => $tn{$_}->inserts,
);
} @tracks;
}
sub delete_effect_profile {
logsub((caller(0))[3]);
my $name = shift;
Audio::Nama::pager( qq(deleting effect profile: $name) );
map{ $_->destroy} Audio::Nama::EffectChain::find( profile => $name );
}
sub apply_effect_profile { # overwriting current effects
logsub((caller(0))[3]);
my ($profile) = @_;
my @chains = Audio::Nama::EffectChain::find(profile => $profile);
# add missing tracks
map{ Audio::Nama::pager( "adding track $_" ); add_track($_) }
grep{ !$tn{$_} }
map{ $_->track_name } @chains;
# add effect chains
map{ $_->add } @chains;
}
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
use v5.36;
use Audio::Nama::Util qw(round);
no warnings 'uninitialized';
## register data about LADSPA plugins, and Ecasound effects and
# presets (names, ids, parameters, hints)
sub prepare_static_effects_data{
my $source = shift;
logsub((caller(0))[3]);
if (not is_test_script() ){
logpkg(__FILE__,__LINE__,'debug', join "\n", "newplugins:", new_plugins());
if (! $source and ($config->{opts}->{r} or new_plugins())){
rename $file->effects_cache, $file->effects_cache . ".bak";
print "Regenerating effects data cache\n";
}
}
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
# timestamp that file was modified
my $filename = shift;
#print "file: $filename\n";
my @s = stat $filename;
$s[9];
}
sub initialize_effect_index {
$fx_cache->{partial_label_to_full} = {};
}
sub generate_mappings_for_shortcuts {
logsub((caller(0))[3]);
map{
my $code = $_;
# abbrevations for lv2: lv2-foo for elv2:http://something.com/other/foo
if ( my ($suffix) = $code =~ /(?:elv2:).*?([^\/]+)$/ )
{
$fx_cache->{partial_label_to_full}->{"lv2-$suffix"} = $code;
}
else {
my ($short) = $code =~ /:([-\w]+)/;
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
{ my %dispatch =
(
ctrl => \&generate_help,
lv2 => \&generate_lv2_help,
ladspa => \&generate_ladspa_help,
cop => \&generate_help,
preset => \&generate_help,
);
sub extract_effects_data {
logsub((caller(0))[3]);
my ($plugin_type, $lower, $upper, $regex, $separator, @lines) = @_;
carp ("incorrect number of lines ", join ' ',$upper-$lower,scalar @lines)
if $lower + @lines - 1 != $upper;
logpkg(__FILE__,__LINE__,'debug',"lower: $lower upper: $upper separator: $separator");
logpkg(__FILE__,__LINE__,'debug', "lines: ". join "\n",@lines);
logpkg(__FILE__,__LINE__,'debug', "regex: $regex");
my $j = $lower - 1;
while(my $line = shift @lines){
$j++;
$line =~ /$regex/ or
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
$fx_cache->{registry}->[$j]->{display} = qq(field);
$fx_cache->{registry}->[$j]->{plugin_type} = $plugin_type;
$fx_cache->{user_help}->[$j] = $dispatch{$plugin_type}->($line);
map{ push @{$fx_cache->{registry}->[$j]->{params}}, {name => $_} } @p_names
if @p_names;
}
}
}
sub sort_ladspa_effects {
logsub((caller(0))[3]);
# print json_out($fx_cache->{split});
my $aa = $fx_cache->{split}->{ladspa}{a};
my $zz = $fx_cache->{split}->{ladspa}{z};
# print "start: $aa end $zz\n";
map{push @{$fx_cache->{ladspa_sorted}}, 0} ( 1 .. $aa ); # fills array slice [0..$aa-1]
splice @{$fx_cache->{ladspa_sorted}}, $aa, 0,
sort { $fx_cache->{registry}->[$a]->{name} cmp $fx_cache->{registry}->[$b]->{name} } ($aa .. $zz) ;
logpkg(__FILE__,__LINE__,'debug', "sorted array length: ". scalar @{$fx_cache->{ladspa_sorted}});
}
sub run_external_ecasound_cmd {
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
$output =~ s/\r//;
$output =~ s/^.+?Registered \w+ plugins:\s*//s; # XXX HARDCODED for plugins only
$output =~ s/^ecasound .+?\Z//ms;
my @output = split "\n",$output;
#splice @output, 0,8;
#splice @output, -3,3;
join "\n",@output;
}
sub read_in_effects_data {
logsub((caller(0))[3]);
#### LADSPA
my $lr = run_external_ecasound_cmd('ladspa-register') ;
logpkg(__FILE__,__LINE__,'debug',"ladpsa-register output:\n",$lr);
my @ladspa = split "\n", $lr;
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
$fx_cache->{registry}->[ $fx_cache->{full_label_to_index}->{ $code } ] = $hashref;
}
}
sub ladspa_path {
$ENV{LADSPA_PATH} || q(/usr/lib/ladspa);
}
sub lv2_path {
$ENV{LV2_PATH} || q(/usr/lib/lv2);
}
sub get_ladspa_hints{
logsub((caller(0))[3]);
my @dirs = split ':', ladspa_path();
my $data = '';
my %seen = ();
my @plugins = ladspa_plugin_list();
#pager join $/, @plugins;
# use these regexes to snarf data
my $pluginre = qr/
Plugin\ Name: \s+ "([^"]+)" \s+
lib/Audio/Nama/EffectsRegistry.pm view on Meta::CPAN
}
$resolution = d2( $resolution + 0.002) if $resolution < 1 and $resolution > 0.01;
$resolution = dn ( $resolution, 3 ) if $resolution < 0.01;
$resolution = int ($resolution + 0.1) if $resolution > 1 ;
($beg, $end, $default, $resolution)
}
sub integrate_ladspa_hints {
logsub((caller(0))[3]);
map{
my $i = $fx_cache->{full_label_to_index}->{$_};
# print("$_ not found\n"),
if ($i) {
$fx_cache->{registry}->[$i]->{params} = $fx_cache->{ladspa}->{$_}->{params};
# we revise the number of parameters read in from ladspa-register
$fx_cache->{registry}->[$i]->{count} = scalar @{$fx_cache->{ladspa}->{$_}->{params}};
$fx_cache->{registry}->[$i]->{display} = $fx_cache->{ladspa}->{$_}->{display};
}
} keys %{$fx_cache->{ladspa}};
lib/Audio/Nama/Engine.pm view on Meta::CPAN
our @ISA = 'Audio::Nama::Engine';
use Role::Tiny::With;
with 'Audio::Nama::EcasoundRun';
sub launch_ecasound_server {
my $self = shift;
Audio::Nama::pager_newline("Using Ecasound via Audio::Ecasound (libecasoundc)");
$self->{audio_ecasound} = Audio::Ecasound->new();
}
sub ecasound_iam{
#logsub((caller(0))[3]);
my $self = shift;
my $cmd = shift;
my $category = Audio::Nama::munge_category(shift());
logit(__LINE__,$category,'debug',"LibEcasound-ECI sent: $cmd");
my (@result) = $self->{audio_ecasound}->eci($cmd);
logit(__LINE__,$category, 'debug',"LibEcasound-ECI got: @result")
if $result[0] and not $cmd =~ /register/ and not $cmd =~ /int-cmd-list/;
my $errmsg = $self->{audio_ecasound}->errmsg();
lib/Audio/Nama/EngineSetup.pm view on Meta::CPAN
# ----------- Engine Setup and Teardown -----------
package Audio::Nama;
use v5.36; use Carp;
sub reconfigure_engine {
logsub((caller(0))[3]);
# skip if command line option is set
return if $config->{opts}->{R};
refresh_wav_cache();
update_jack_client_list();
refresh_tempo_map() if $config->{use_metronome};
project_snapshot();
Audio::Nama::Engine::sync_action('configure');
}
sub request_setup {
my ($package, $filename, $line) = caller();
logpkg(__FILE__,__LINE__,'debug',"reconfigure requested in file $filename:$line");
$setup->{changed}++
}
sub generate_setup {Audio::Nama::Engine::sync_action('setup') }
sub start_transport {
logsub((caller(0))[3]);
Audio::Nama::Engine::sync_action('start');
}
sub stop_transport {
logsub((caller(0))[3]);
Audio::Nama::Engine::sync_action('stop');
}
1;
__END__
lib/Audio/Nama/Git.pm view on Meta::CPAN
$config->{use_git} or warn("@_: git command, but git is not enabled.
You may want to set use_git: 1 in .namarc"), return;
logpkg(__FILE__,__LINE__,'debug',"VCS command: git @_");
$project->{repo}->run(@_)
}
sub repo_git_dir { join_path(project_dir(),'.git') }
sub init_repo_obj { $project->{repo} = Git::Repository->new( work_tree => project_dir() ) }
sub create_repo { Git::Repository->run( init => project_dir() )}
sub initialize_project_repository {
logsub((caller(0))[3]);
Audio::Nama::throw("either a test script running or use_git: 0 is configured in .namarc. ",
"No repo created for project ", project_dir()),
return if not $config->{use_git} or is_test_script();
if (not -d repo_git_dir()){
pager("Creating git repository in ". repo_git_dir());
create_repo;
init_repo_obj();
create_file_stubs();
git_commit('initialize_repository');
} else {
init_repo_obj();
#git_commit('committing prior changes') if git_diff();
}
}
sub git_diff {
my @files = @_;
my $diff = git('diff', @files);
$diff =~ /\S/ ? $diff : undef
}
sub git_tag_exists {
logsub((caller(0))[3]);
my $tag = shift;
grep { $tag eq $_ } git( 'tag','--list');
}
# on command "get foo", Nama opens a branch name 'foo-branch',
# or returns to HEAD of existing branch 'foo-branch'
sub tag_branch { "$_[0]-branch" }
sub restore_state_from_vcs {
logsub((caller(0))[3]);
my $name = shift; # tag or branch
# checkout branch if matching branch exists
if (git_branch_exists($name)){
pager_newline( qq($name: branch exists. Checking out branch $name.) );
git_checkout($name);
}
lib/Audio/Nama/Git.pm view on Meta::CPAN
git_create_branch($branch, $tag);
}
}
else { throw("$name: tag doesn't exist. Cannot checkout."), return }
restore_state_from_file();
}
sub project_snapshot {
logsub((caller(0))[3]);
return if $config->{opts}->{R}
or $this_engine->running and Audio::Nama::ChainSetup::really_recording();
# we skip storing commands that do not affect project state
save_state();
reset_command_buffer(), return if not state_changed();
return if not $config->{use_git}
or not $project->{name}
or not $project->{repo};
lib/Audio/Nama/Git.pm view on Meta::CPAN
scalar @{$project->{command_buffer}} and join("\n",
undef,
(map{ $_->{command} } @{$project->{command_buffer}}),
# context for first command of group
"* track: $project->{command_buffer}->[0]->{context}->{track}",
"* bus: $project->{command_buffer}->[0]->{context}->{bus}",
"* op: $project->{command_buffer}->[0]->{context}->{op}"
) or undef
}
sub git_commit {
logsub((caller(0))[3]);
my $commit_message = shift;
my @defaults = ($file->state_store, $file->midi_store, $file->tempo_map);
my @files = scalar @_ ? @_ : @defaults;
no warnings 'uninitialized';
@files = @defaults if not @files;
$commit_message .= command_buffer_contents();
use utf8;
git( add => @files);
git( commit => '--quiet', '--message', $commit_message);
reset_command_buffer();
}
sub git_checkout {
logsub((caller(0))[3]);
my ($branchname, @args) = @_;
return unless $config->{use_git};
my $exist_message = git_branch_exists($branchname)
? undef
: "$branchname: branch does not exist.";
my $dirty_tree_msg = !! state_changed()
? "You have changes to working files.
You cannot switch branches until you commit
these changes, or throw them away."
lib/Audio/Nama/Git.pm view on Meta::CPAN
$conjunction,
$exist_message,
"No action taken."), return
if $dirty_tree_msg or $exist_message;
git(checkout => $branchname, @args);
}
sub git_create_branch {
logsub((caller(0))[3]);
my ($branchname, $branchfrom) = @_;
return unless $config->{use_git};
# create new branch
my @args;
my $from_target;
$from_target = "from $branchfrom" if $branchfrom;
push @args, $branchname;
push(@args, $branchfrom) if $branchfrom;
pager("Creating branch $branchname $from_target");
git(checkout => '-b', @args)
}
sub state_changed {
logsub((caller(0))[3]);
return unless $config->{use_git};
git_diff();
}
sub git_branch_exists {
logsub((caller(0))[3]);
return unless $config->{use_git};
my $branchname = shift;
grep{ $_ eq $branchname }
map{ s/^\s+//; s/^\* //; $_}
git("branch");
}
sub current_branch {
logsub((caller(0))[3]);
return unless $project->{repo};
my ($b) = map{ /\* (\S+)/ } grep{ /\*/ } split "\n", git('branch');
$b
}
sub git_sha {
my $commit = shift || 'HEAD';
my ($sha) = git(show => $commit) =~ /commit ([0-9a-f]{10})/;
$sha
}
sub git_branch_display {
logsub((caller(0))[3]);
my $display = $Audio::Nama::project->{name};
return $display unless $config->{use_git};
my $cb = current_branch();
$display .= ":$cb" if $cb and $cb ne 'master';
$display
}
sub list_branches {
pager_newline(
"---Branches--- (asterisk marks current branch)",
$project->{repo}->run('branch'),
lib/Audio/Nama/Grammar.pm view on Meta::CPAN
# --------------------- Command Grammar ----------------------
package Audio::Nama;
use Audio::Nama::Effect qw(:all);
use v5.36;
sub setup_grammar {
### COMMAND LINE PARSER
logsub((caller(0))[3]);
$text->{commands_yml} = get_data_section("commands_yml");
$text->{commands_yml} = quote_yaml_scalars($text->{commands_yml});
$text->{commands} = yaml_in( $text->{commands_yml}) ;
map
{
my $full_name = $_;
my $shortcuts = $text->{commands}->{$full_name}->{short};
my @shortcuts = ();
@shortcuts = split " ", $shortcuts if $shortcuts;
lib/Audio/Nama/Grammar.pm view on Meta::CPAN
{
map{ 'm'.$_, 1} grep{ !$skip{$_} } split " ", get_data_section("midi_commands")
};
for (keys %{$text->{midi_cmd}}){
say "$_: midi command same as Nama command" if $text->{commands}->{$_}
}
}
sub process_line {
state $total_effects_count;
logsub((caller(0))[3]);
no warnings 'uninitialized';
my ($user_input) = @_;
logpkg(__FILE__,__LINE__,'debug',"user input: $user_input");
if (defined $user_input and $user_input !~ /^\s*$/) {
push $text->{command_history}->@*, $user_input;
$text->{command_index}++;
# convert hyphenated commands to underscore form
while( my($from, $to) = each %{$text->{hyphenated_commands}} ){ $user_input =~ s/$from/$to/g }
my $context = context();
my $success = nama_cmd( $user_input );
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
# gui handling
# in the $gui variable, keys with leading _underscore
# indicate variables
#
# $gui->{_project_name} # scalar/array/hash var
# $gui->{mw} # Tk objects (widgets, frames, etc.)
sub init_gui {
logsub((caller(0))[3]);
init_palettefields(); # keys only
### Tk root window
# Tk main window
$gui->{mw} = MainWindow->new;
get_saved_colors();
$gui->{mw}->optionAdd('*font', 'Helvetica 12');
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
sub wh {
my $widget = shift;
$widget->update;
my ($width,$height,$sign1,$xpos,$sign2,$ypos)
= $widget->geometry =~ /(\d+)x(\d+)([+-])(\d+)([+-])(\d+)/;
$width,$height
}
sub transport_gui {
my $ui = shift;
logsub((caller(0))[3]);
$gui->{engine_label} = $gui->{transport_frame}->Label(
-text => 'TRANSPORT',
-width => 12,
)->pack(-side => 'left');;
$gui->{engine_start} = $gui->{transport_frame}->Button->pack(-side => 'left');
$gui->{engine_stop} = $gui->{transport_frame}->Button->pack(-side => 'left');
$gui->{engine_stop}->configure(-text => "Stop",
-command => sub {
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
$ui->project_label_configure(-background => $color);
start_transport();
});
#preview_button();
#mastering_button();
}
sub time_gui {
my $ui = shift;
logsub((caller(0))[3]);
my $time_label = $gui->{clock_frame}->Label(
-text => 'TIME',
-width => 12);
#print "bg: $gui->{_nama_palette}->{ClockBackground}, fg:$gui->{_nama_palette}->{ClockForeground}\n";
$gui->{clock} = $gui->{clock_frame}->Label(
-text => '0:00',
-width => 8,
-background => $gui->{_nama_palette}->{ClockBackground},
-foreground => $gui->{_nama_palette}->{ClockForeground},
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
$bn{Main}->set(version => $v);
$version->configure(-text => $v);
Audio::Nama::reconfigure_engine();
refresh();
}
);
}
}
sub track_gui {
logsub((caller(0))[3]);
my $ui = shift;
my $n = shift;
pager("track_gui already generated"), return
if defined $gui->{tracks}->{$n} ;
return if $ti{$n}->hide;
logpkg(__FILE__,__LINE__,'debug', "found index: $n");
my @rw_items = @_ ? @_ : (
[ 'command' => "REC",
-foreground => 'red',
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
)
];
$ui->refresh_track($n);
}
sub remove_track_gui {
my $ui = shift;
my $n = shift;
logsub((caller(0))[3]);
return unless $gui->{tracks_remove}->{$n};
map {$_->destroy } @{ $gui->{tracks_remove}->{$n} };
delete $gui->{tracks_remove}->{$n};
delete $gui->{tracks}->{$n};
}
sub paint_mute_buttons {
map{ $gui->{tracks}->{$_}{mute}->configure(
-background => $gui->{_nama_palette}->{Mute},
)} grep { $ti{$_}->old_vol_level}# muted tracks
map { $_->n } Audio::Nama::audio_tracks(); # track numbers
}
sub create_master_and_mix_tracks {
logsub((caller(0))[3]);
my @rw_items = (
[ 'command' => "MON",
-command => sub {
return if $this_engine->started();
$tn{Main}->set(rw => "MON");
$ui->refresh_track($tn{Main}->n);
}],
[ 'command' => "OFF",
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
$w->radiobutton(
-label => $v,
-value => $v,
-command =>
sub { $gui->{tracks}->{$n}->{version}->configure(-text=>$v)
unless $ti{$n}->rec }
);
}
sub add_effect_gui {
logsub((caller(0))[3]);
my $ui = shift;
my %p = %{shift()};
my ($n,$code,$id,$parent,$parameter,$FX) =
@p{qw(chain type id parent parameter self)};
my $i = $fx_cache->{full_label_to_index}->{$code};
logpkg(__FILE__,__LINE__,'debug', sub{json_out(\%p)});
logpkg(__FILE__,__LINE__,'debug', "id: $id, parent: $parent");
# $id is determined by effect_init, which will return the
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
map{ $_->destroy } map{ $_->children } $gui->{fx_frame};
#my @children = $gui->{group_frame}->children;
#map{ $_->destroy } @children[1..$#children];
my @children = $gui->{track_frame}->children;
# leave field labels (first row)
map{ $_->destroy } @children[11..$#children]; # fragile
%{$gui->{marks}} and map{ $_->destroy } values %{$gui->{marks}};
}
sub remove_effect_gui {
my $ui = shift;
logsub((caller(0))[3]);
my $id = shift;
my $FX = fxn($id);
my $n = $FX->chain;
logpkg(__FILE__,__LINE__,'debug', "id: $id, chain: $n");
logpkg(__FILE__,__LINE__,'debug', "i have widgets for these ids: ", join " ",keys %{$gui->{fx}});
logpkg(__FILE__,__LINE__,'debug', "preparing to destroy: $id");
return unless defined $gui->{fx}->{$id};
$gui->{fx}->{$id}->destroy();
delete $gui->{fx}->{$id};
}
sub effect_button {
logsub((caller(0))[3]);
my ($n, $label, $start, $end) = @_;
logpkg(__FILE__,__LINE__,'debug', "chain $n label $label start $start end $end");
my @items;
my $widget;
my @indices = ($start..$end);
if ($start >= $fx_cache->{split}->{ladspa}{a} and $start <= $fx_cache->{split}->{ladspa}{z}){
@indices = ();
@indices = @{$fx_cache->{ladspa_sorted}}[$start..$end];
logpkg(__FILE__,__LINE__,'debug', "length sorted indices list: ",scalar @indices );
logpkg(__FILE__,__LINE__,'debug', "Indices: @indices");
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
-text => $label,
-tearoff =>0,
# -relief => 'raised',
-menuitems => [@items],
);
$widget;
}
sub make_scale {
logsub((caller(0))[3]);
my $ref = shift;
my %p = %{$ref};
# %p contains following:
# id => operator id
# parent => parent widget, i.e. the frame
# p_num => parameter number, starting at 0
# length => length widget # optional
my $id = $p{id};
my $FX = fxn($id) // $p{self};
my $n = $FX->chain;
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
$gui->{wwcanvas}->createLine(
$xpos,0,
$xpos,$config->{waveform_canvas_y},
-fill => 'red',
-width => 1,
-tags => 'playback-indicator'
);
}
sub get_saved_colors {
logsub((caller(0))[3]);
# aliases
$gui->{_old_bg} = $gui->{_palette}{mw}{background};
$gui->{_old_abg} = $gui->{_palette}{mw}{activeBackground};
$gui->{_old_bg} //= '#d915cc1bc3cf';
#print "pb: $gui->{_palette}{mw}{background}\n";
my $pal = $file->gui_palette;
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
-background => $gui->{_nama_palette}->{GroupBackground},
-foreground => $gui->{_nama_palette}->{GroupForeground},
);
refresh();
}
}
}
sub colorchooser {
logsub((caller(0))[3]);
my ($field, $initialcolor) = @_;
logpkg(__FILE__,__LINE__,'debug', "field: $field, initial color: $initialcolor");
my $new_color = $gui->{mw}->chooseColor(
-title => $field,
-initialcolor => $initialcolor,
);
#print "new color: $new_color\n";
$new_color;
}
sub init_palettefields {
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
my %rw_background = ( REC => $gui->{_nama_palette}->{RecBackground},
PLAY => $gui->{_nama_palette}->{MonBackground},
MON => $gui->{_nama_palette}->{MonBackground},
OFF => $gui->{_nama_palette}->{OffBackground});
$widget->configure( -background => $rw_background{$status} );
$widget->configure( -foreground => $rw_foreground{$status} );
}
sub refresh_group {
# main group, in this case we want to skip null group
logsub((caller(0))[3]);
my $status;
if ( grep{ $_->rec}
map{ $tn{$_} }
$bn{Main}->tracks ){
$status = REC
}elsif( grep{ $_->play}
lib/Audio/Nama/Graphical.pm view on Meta::CPAN
croak "some crazy status |$status|\n" if $status !~ m/rec|mon|off/i;
#logit(__LINE__,'Audio::Nama::Refresh','debug', "attempting to set $status color: ", $take_color{$status});
set_widget_color( $gui->{group_rw}, $status) if $gui->{group_rw};
}
sub refresh_track {
my $ui = shift;
my $n = shift;
logsub((caller(0))[3]);
my $rec_status = $ti{$n}->rec_status;
logit(__LINE__,'Audio::Nama::Refresh','debug', "track: $n rec_status: $rec_status");
return unless $gui->{tracks}->{$n}; # hidden track
# set the text for displayed fields
$gui->{tracks}->{$n}->{rw}->configure(-text => $rec_status);
$gui->{tracks}->{$n}->{ch_r}->configure( -text =>
lib/Audio/Nama/Initializations.pm view on Meta::CPAN
$setup->{_last_rec_tracks} = [];
$mastering->{track_names} = [ qw(Eq Low Mid High Boost) ];
init_wav_memoize() if $config->{memoize};
}
sub initialize_interfaces {
logsub((caller(0))[3]);
if ( $config->{opts}->{g}){
Audio::Nama::Graphical::initialize_tk() and $ui = Audio::Nama::Graphical->new()
or pager_newline( "Unable to load perl Tk module. Starting in console mode.")
}
if ( not defined $ui ){
$ui = Audio::Nama::Text->new();
$text->{loop} = IO::Async::Loop->new;
}
choose_sleep_routine();
lib/Audio/Nama/Jack.pm view on Meta::CPAN
# ------- Jack port connect routines -------
package Audio::Nama;
use v5.36;
use File::Slurp;
no warnings 'uninitialized';
# general functions
sub update_jack_client_list {
state $warn_count;
#logsub((caller(0))[3]);
# cache current JACK status
# skip if Ecasound is busy
return if $this_engine->started();
if( $jack->{jackd_running} = process_is_running('jackd') ){
# reset our clients data
$jack->{clients} = {};
$jack->{use_jacks}
lib/Audio/Nama/Jack.pm view on Meta::CPAN
sub jack_client_array {
# returns array of ports if client and direction exist
my ($name, $direction) = @_;
$jack->{clients}->{$name}{$direction} // []
}
sub jacks_get_port_latency {
logsub((caller(0))[3]);
delete $jack->{clients};
my $jc;
$jc = jacks::JsClient->new("watch latency", undef, $jacks::JackNullOption, 0);
my $plist = $jc->getPortNames(".");
for (my $i = 0; $i < $plist->length(); $i++) {
my $pname = $plist->get($i);
lib/Audio/Nama/Jack.pm view on Meta::CPAN
} @ports;
}
sub parse_ports_list {
# default to output of jack_lsp -p
logsub((caller(0))[3]);
my $j = shift || qx(jack_lsp -tp 2> /dev/null);
logpkg(__FILE__,__LINE__,'debug', "input: $j");
# convert to single lines
$j =~ s/\n\s+/ /sg;
# system:capture_1 alsa_pcm:capture_1 properties: output,physical,terminal,
#fluidsynth:left properties: output,
#fluidsynth:right properties: output,
lib/Audio/Nama/Latency.pm view on Meta::CPAN
remove_connections_to_wav_out($lg);
# want to deal with specific ports,
# so substitute them into the graph
replace_terminals_by_jack_ports($lg);
}
sub propagate_latency {
logsub((caller(0))[3]);
initialize_jack_graph();
logpkg(__FILE__,__LINE__,'debug',"jack graph\n","$lg");
parse_port_connections();
start_latency_watcher();
propagate_capture_latency();
#propagate_playback_latency();
}
sub propagate_capture_latency {
my @sinks = grep{ $lg->is_sink_vertex($_) } $lg->vertices();
logpkg(__FILE__,__LINE__,'debug',"recurse through latency graph starting at sinks: @sinks");
latency_rememoize();
map{ latency_of($lg,'capture',$_) } @sinks;
}
sub propagate_playback_latency {
logsub((caller(0))[3]);
logpkg(__FILE__,__LINE__,'debug',"jack graph\n","$lg");
my @sources = grep{ $lg->is_source_vertex($_) } $lg->vertices();
logpkg(__FILE__,__LINE__,'debug',"recurse through latency graph starting at sources: @sources");
latency_rememoize();
map{ latency_of($lg,'playback',$_) } @sources;
}
sub predecessor_latency {
scalar @_ > 2 and die "too many args to predecessor_latency: @_";
my ($g, $v) = @_;
lib/Audio/Nama/Mark.pm view on Meta::CPAN
}
# ---------- Mark and jump routines --------
{
package Audio::Nama;
use v5.36;
use Audio::Nama::Globals qw(:all);
sub drop_mark {
logsub((caller(0))[3]);
my $name = shift;
my $here = ecasound_iam("getpos");
if( my $mark = $Audio::Nama::Mark::by_name{$name}){
pager("$name: a mark with this name exists already at: ",
colonize($mark->time));
return
}
if( my ($mark) = grep { $_->time == $here} Audio::Nama::Mark::all()){
pager( q(This position is already marked by "),$mark->name,q(") );
return
}
my $mark = Audio::Nama::Mark->new( time => $here,
name => $name);
$ui->marker($mark); # for GUI
}
sub mark { # GUI_CODE
logsub((caller(0))[3]);
my $mark = shift;
my $pos = $mark->time;
if ($gui->{_markers_armed}){
$ui->destroy_marker($pos);
$mark->remove;
arm_mark_toggle(); # disarm
}
else{
set_position($pos);
}
}
sub next_mark {
logsub((caller(0))[3]);
my $mark = next_mark_object();
set_position($mark->time);
$this_mark = $mark;
}
sub next_mark_object {
my @marks = Audio::Nama::Mark::all();
my $here = ecasound_iam("cs-get-position");
for my $i ( 0..$#marks ){
if ($marks[$i]->time - $here > 0.001 ){
logpkg(__FILE__,__LINE__,'debug', "here: $here, future time: ", $marks[$i]->time);
lib/Audio/Nama/Mark.pm view on Meta::CPAN
sub previous_mark_object {
my @marks = Audio::Nama::Mark::all();
my $here = ecasound_iam("cs-get-position");
for my $i ( reverse 0..$#marks ){
if ($marks[$i]->time < $here ){
return $marks[$i];
}
}
}
sub previous_mark {
logsub((caller(0))[3]);
my $mark = previous_mark_object();
set_position($mark->time);
$this_mark = $mark;
}
sub modify_mark {
my ($mark, $newtime, $quiet) = @_;
$mark->set( time => $newtime );
! $quiet && do {
pager($mark->name, ": set to ", d2( $newtime), "\n");
lib/Audio/Nama/Mark.pm view on Meta::CPAN
sub bump_mark_plus_1 { }
sub bump_mark_minus_point_1 { }
sub bump_mark_plus_point_1 { }
sub bump_mark_minus_point_01 { }
sub bump_mark_plus_point_01 { }
## jump playback head position
sub jump_to_start {
logsub((caller(0))[3]);
return if Audio::Nama::ChainSetup::really_recording();
jump( 0 );
}
sub jump_to_end {
logsub((caller(0))[3]);
# ten seconds shy of end
return if Audio::Nama::ChainSetup::really_recording();
my $end = ecasound_iam('cs-get-length') - $config->{seek_end_margin} ;
jump($end);
}
sub jump {
return if Audio::Nama::ChainSetup::really_recording();
my $delta = shift;
logsub((caller(0))[3]);
my $here = ecasound_iam('getpos');
logpkg(__FILE__,__LINE__,'debug', "delta: $delta, here: $here");
my $new_pos = $here + $delta;
if ( $setup->{audio_length} )
{
$new_pos = $new_pos < $setup->{audio_length}
? $new_pos
: $setup->{audio_length} - 10
}
set_position_with_fade( $new_pos );
}
sub set_position_with_fade { fade_around(\&_set_position, @_) }
sub _set_position {
logsub((caller(0))[3]);
return if Audio::Nama::ChainSetup::really_recording(); # don't allow seek while recording
my $seconds = shift;
my $coderef = sub{ ecasound_iam("setpos $seconds") };
$jack->{jackd_running}
? Audio::Nama::stop_do_start( $coderef, $jack->{seek_delay} )
: $coderef->();
lib/Audio/Nama/Midi.pm view on Meta::CPAN
use v5.36;
#use Audio::Nama::Log qw(logpkg);
use Carp;
{
my ($pid, $sel);
my @handles = my ($fh_midi_write, $fh_midi_read, $fh_midi_error) = map{ IO::Handle->new() } 1..3;
map{ $_->autoflush(1) } @handles;
sub start_midish_process {
logsub((caller(0))[3]);
my $executable = qx(which midish);
chomp $executable;
$executable or say("Midish not found!"), return;
$pid = open3($fh_midi_write, $fh_midi_read, $fh_midi_error,"$executable -v")
or warn "Midish failed to start!";
$sel = IO::Select->new();
$sel->add($fh_midi_read);
$sel->add($fh_midi_error);
midish_cmd( qq(print "Midish is ready.") );
write_aux_midi_commands();
midish_cmd( q(exec ").$file->aux_midi_commands.q(") );
$pid
}
sub midish_cmd {
my $command = shift;
logsub((caller(0))[3]);
return unless $config->{use_midi};
print $fh_midi_write "$command\n";
#say "applied midish command: $command";
$project->{midi_history} //=[];
push @{ $project->{midi_history} },$command;
my $length = 2**16;
sleeper(0.05);
my @result;
lib/Audio/Nama/Modes.pm view on Meta::CPAN
# ----------- Modes: mastering, preview, doodle ---------
package Audio::Nama;
use v5.36;
{
sub set_preview_mode {
# set preview mode, releasing doodle mode if necessary
logsub((caller(0))[3]);
# do nothing if already in 'preview' mode
return if $mode->preview;
disable_preview_modes();
{
no warnings 'uninitialized';
$mode->{preview}++;
}
pager( <<'MSG');
Setting preview mode. Recording of audio files is disabled.
Type 'arm' to enable recording.
MSG
}
sub set_doodle_mode {
logsub((caller(0))[3]);
return if $this_engine->started() and Audio::Nama::ChainSetup::really_recording();
disable_preview_modes();
{
no warnings 'uninitialized';
$mode->{doodle}++;
}
$tn{Mixdown}->set(rw => OFF);
# reconfigure_engine will generate setup and start transport
pager( <<'MSG' );
Setting doodle mode. Using live inputs only. Duplicate
inputs are excluded. Recording of audio files is disabled.
Exit using 'preview' or 'arm' commands
MSG
}
sub exit_preview_modes {
logsub((caller(0))[3]);
return unless $mode->{preview} or $mode->{doodle};
disable_preview_modes();
stop_transport();
pager("Exiting preview/doodle mode");
}
sub disable_preview_modes {
undef $mode->{preview};
undef $mode->{doodle};
}
lib/Audio/Nama/Persistence.pm view on Meta::CPAN
# ---------- Persistent State Support -------------
package Audio::Nama;
use File::Copy;
use v5.36; no warnings 'uninitialized';
use vars '$VERSION';
sub save_state {
logsub((caller(0))[3]);
my $filename = shift;
my $path = $filename || $file->state_store();
# remove extension if present
$filename =~ s/\.json//;
# append filename if warranted
$filename =
lib/Audio/Nama/Persistence.pm view on Meta::CPAN
sub decode {
my ($source, $suffix) = @_;
$decode{$suffix}
or die qq(key $suffix: expecting one of).join q(,),keys %decode;
$decode{$suffix}->($source);
}
}
sub restore_state_from_file {
logsub((caller(0))[3]);
my $filename = shift;
$filename //= $file->state_store();
initialize_marshalling_arrays();
my $suffix = 'json';
my $path = $file->untracked_state_store;
if (-r $path)
{
my $source = read_file($path);
lib/Audio/Nama/Persistence.pm view on Meta::CPAN
file => $file->global_effect_chains,
format => $format,
vars => \@global_effect_chain_vars,
class => 'Audio::Nama',
);
} $config->serialize_formats;
}
sub restore_global_effect_chains {
logsub((caller(0))[3]);
my $path = $file->global_effect_chains;
-r $path or return;
my $source = read_file($path);
throw("$path: empty file"), return unless $source;
my $suffix = 'json';
my $ref = decode($source, $suffix);
assign(
data => $ref,
vars => \@global_effect_chain_vars,
class => 'Audio::Nama');
lib/Audio/Nama/Project.pm view on Meta::CPAN
my ($vol, $dir, $lastdir) = File::Spec->splitpath($_); $lastdir
} File::Find::Rule ->directory()
->maxdepth(1)
->extras( { follow => 1} )
->in( project_root());
pager($projects);
}
sub initialize_project_data {
logsub((caller(0))[3]);
-d Audio::Nama::waveform_dir() or mkdir Audio::Nama::waveform_dir();
$ui->destroy_widgets();
$ui->project_label_configure(
-text => uc $project->{name},
-background => 'lightyellow',
);
$gui->{tracks} = {};
$gui->{fx} = {};
lib/Audio/Nama/Project.pm view on Meta::CPAN
sub create_project_dirs {
map{create_dir($_)} project_dir(), this_wav_dir(), waveform_dir()
}
sub create_file_stubs {
write_file($file->state_store, "{}\n") unless -e $file->state_store;
write_file($file->midi_store, "\n") unless -e $file->midi_store;
write_file($file->tempo_map, "\n") unless -e $file->tempo_map;
}
sub load_project {
logsub((caller(0))[3]);
my %args = @_;
logpkg(__FILE__,__LINE__,'debug', sub{json_out \%args});
$project->{name} = $args{name};
if (not $project->{name} or not -d project_dir() and not $args{create})
{
no warnings 'uninitialized';
Audio::Nama::pager_newline(qq(Project "$project->{name}" not found. Loading project "untitled".));
load_project(name => 'Untitled', create => 1);
lib/Audio/Nama/Project.pm view on Meta::CPAN
#$ui->global_version_buttons();
#$ui->refresh_group;
logpkg(__FILE__,__LINE__,'debug', "project_root: ", project_root());
logpkg(__FILE__,__LINE__,'debug', "this_wav_dir: ", this_wav_dir());
logpkg(__FILE__,__LINE__,'debug', "project_dir: ", project_dir());
1;
}
sub restore_state {
logsub((caller(0))[3]);
my $name = shift;
if( ! $name or $name =~ /.json$/ or ! $config->{use_git})
{
restore_state_from_file($name)
}
else { restore_state_from_vcs($name) }
}
sub initialize_mixer {
return if $tn{Main};
lib/Audio/Nama/Project.pm view on Meta::CPAN
send_type => 'soundcard',
send_id => 1,
);
$ui->create_master_and_mix_tracks();
}
sub dig_ruins {
logsub((caller(0))[3]);
return if user_tracks_present();
logpkg(__FILE__,__LINE__,'debug', "looking for audio files");
my $d = this_wav_dir();
opendir my $wav, $d or carp "couldn't open directory $d: $!";
# remove version numbers
my @wavs = grep{s/(_\d+)?\.wav$//i} readdir $wav;
lib/Audio/Nama/Project.pm view on Meta::CPAN
map{add_track($_)}@wavs;
}
sub remove_riff_header_stubs {
# 44 byte stubs left by a recording chainsetup that is
# connected by not started
logsub((caller(0))[3]);
logpkg(__FILE__,__LINE__,'debug', "this wav dir: ", this_wav_dir());
return unless this_wav_dir();
my @wavs = File::Find::Rule ->name( qr/\.wav$/i )
->file()
->size(44)
->extras( { follow => 1} )
->in( this_wav_dir() )
if -d this_wav_dir();
logpkg(__FILE__,__LINE__,'debug', join $/, @wavs);
map { unlink $_ } @wavs;
}
sub create_system_buses {
logsub((caller(0))[3]);
my $buses = q(
Main Audio::Nama::SubBus send_type track send_id Main # master fader track
Mixdown Audio::Nama::Bus # mixdown track
Mastering Audio::Nama::Bus # mastering network
Insert Audio::Nama::Bus # auxiliary tracks for inserts
Cooked Audio::Nama::Bus # for track caching
Temp Audio::Nama::Bus # temp tracks while generating setup
Null Audio::Nama::Bus # unrouted for Main
Midi Audio::Nama::MidiBus send_type null send_id null # all MIDI tracks
lib/Audio/Nama/Terminal.pm view on Meta::CPAN
# $label->set_text("lehho");
#prompt();
}
sub print_to_terminal ($txt) {
$vbox->add( Tickit::Widget::Static->new( text => $txt ));
$scrollbox->scroll_to(1e5);
}
sub prompt {
logsub((caller(0))[3]);
my $obj = shift;
my $prompt = join ' ', 'nama', git_branch_display(), bus_track_display(),'> ';
#$obj->set_text($prompt);
#$obj->set_position(99);
}
sub next_command {
$text->{command_index}++ unless $text->{command_index} == scalar $text->{command_history}->@*;
print_command();
}
sub previous_command {
lib/Audio/Nama/Terminal.pm view on Meta::CPAN
GETC => sub { },
CLOSE => sub { };
}
sub restore_stdout {
select $old_output_fh;
close FH;
}
=comment
sub prompt {
logsub((caller(0))[3]);
join ' ', 'nama', git_branch_display(), bus_track_display(),'> '
}
=cut
sub end_of_list_sound { system( $config->{hotkey_beep} ) }
sub previous_track {
end_of_list_sound(), return if $this_track->n == 1;
do{ $this_track = $ti{$this_track->n - 1} } until ! $this_track->hide;
}
lib/Audio/Nama/Terminal.pm view on Meta::CPAN
sub next_param {
my $param = $this_track->param;
$param < scalar @{ fxn($this_track->op)->params }
? $project->{current_param}->{$this_track->op}++
: end_of_list_sound()
}
{my $override;
sub revise_prompt {
}
=comment
logsub((caller(0))[3]);
# hack to allow suppressing prompt
$override = ($_[0] eq "default" ? undef : $_[0]) if defined $_[0];
$override//prompt()
=cut
}
sub detect_spacebar {
=comment
if ( $config->{press_space_to_start}
and ($buffer eq $trigger)
and ! ($mode->song or $mode->live) )
toggle_transport();
=cut
warn ("not implemented");
}
sub throw {
logsub((caller(0))[3]);
pager_newline(@_)
}
sub pagers { &pager_newline(join "",@_) } # pass arguments along
sub pager_newline {
# Add a newline if necessary to each line
# push them onto the output buffer
# print them to the screen
lib/Audio/Nama/Terminal.pm view on Meta::CPAN
$config->{use_pager}
and ! $config->{opts}->{T}
}
sub pager {
# push array onto output buffer, add two newlines
# and print on terminal or view in pager
# as appropriate
logsub((caller(0))[3]);
my @output = @_;
@output or return;
chomp $output[-1];
$output[-1] .= "\n\n";
@output = map{"$_\n"} map{ split "\n"} @output;
return unless scalar @output;
print for @output;
}
sub file_pager {};
1;
lib/Audio/Nama/TrackIO.pm view on Meta::CPAN
sub is_used {
my $track = shift; # Track is used if:
my $bus = $track->bus; #
$track->send_type # It's sending its own signal
or $track->{rw} eq REC # It's recording its own signal
or $track->used_by_another_track
or ($bus and $bus->can('wantme') and $bus->wantme) # A bus needs my signal
}
sub rec_status {
# logsub((caller(0))[3]);
my $track = shift;
my $bus = $track->bus;
return OFF if 0 # ! ($track->engine_group eq $Audio::Nama::this_engine->name)
or $track->{rw} eq OFF
or ! $track->is_used
and ! ($mode->doodle and ! $mode->eager and $setup->{tracks_with_duplicate_inputs}->{$track->name} );
if ($track->{rw} ne PLAY) # e.g. MON or REC
{
return OFF if $track->source_type eq 'jack_client'
lib/Audio/Nama/TrackIO.pm view on Meta::CPAN
if $track->project;
$msg .= qq(.\n);
$msg .= "Can't set a track alias to REC.\n";
Audio::Nama::throw($msg);
return;
}
$track->set_rw(REC);
}
sub rw_set {
my $track = shift;
logsub((caller(0))[3]);
my ($bus, $rw) = @_;
$track->set_rec, return if $rw eq REC;
$track->set_rw($rw);
}
sub set_rw {
my ($track, $setting) = @_;
#my $already = $track->rw eq $setting ? " already" : "";
$track->set(rw => $setting);
my $status = $track->rec_status();
Audio::Nama::pagers("Track ",$track->name, " set to $setting",
lib/Audio/Nama/TrackUtils.pm view on Meta::CPAN
package Audio::Nama;
use v5.36;
sub add_track {
logsub((caller(0))[3]);
my ($name, @params) = @_;
my %vals = (name => $name, @params);
my $class = $vals{class} // 'Audio::Nama::Track';
{ no warnings 'uninitialized';
logpkg(__FILE__,__LINE__,'debug', "name: $name, ch_r: $gui->{_chr}, ch_m: $gui->{_chm}");
}
Audio::Nama::throw("$name: track name already in use. Skipping."), return
if $tn{$name};
Audio::Nama::throw("$name: reserved track name. Skipping"), return
if grep $name eq $_, @{$mastering->{track_names}};