Audio-Nama
view release on metacpan or search on metacpan
lib/Audio/Nama/Effect.pm view on Meta::CPAN
# store relationship
my $parent = fxn($parent_id);
my $owns = $parent->owns;
logpkg(__FILE__,__LINE__,'debug',"parent owns @$owns");
# register effect_id with parent unless it is already there
push @$owns, $id unless grep { $id eq $_ } @$owns;
logpkg(__FILE__,__LINE__,'debug',sub{join " ", "my attributes:", json_out($self->as_hash)});
# find position of parent id in the track ops array
# and insert child id immediately afterwards
# unless already present
insert_after_string($parent_id, $id, @{$track->ops})
unless grep {$id eq $_} @{$track->ops}
}
else {
# append effect_id to track list unless already present
push @{$track->ops}, $id unless grep {$id eq $_} @{$track->ops}
}
$self
}
# provide object
{
no warnings 'redefine';
sub parent {
my $self = shift;
fxn($self->{parent});
}
}
sub is_read_only {
my ($self, $param) = @_;
no warnings 'uninitialized';
$self->about->{params}->[$param]->{dir} eq 'output'
}
sub remove_name { my $self = shift; delete $self->{name} }
sub set_name { my $self = shift; $self->{name} = shift }
sub set_surname { my $self = shift; $self->{surname} = shift}
sub is_controller { my $self = shift; $self->parent }
sub is_channel_op { my $self = shift; $Audio::Nama::config->{ecasound_channel_ops}->{$self->type} }
sub has_read_only_param {
my $self = shift;
no warnings 'uninitialized';
my $entry = $self->about;
for(0..scalar @{$entry->{params}} - 1)
{
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
}
no warnings 'uninitialized';
$self->offset + $opcount;
}
sub ecasound_effect_index_ {
my $self = shift;
1 + first_index {$_->id eq $self->id } $self->track->ops_ecasound_order();
}
sub track_effect_index { # the position of the ID in the track's op array
my $self = shift;
my $id = $self->id;
my $pos = first_index {$id eq $_} @{$self->track->ops} ;
$pos
}
sub controllers {
my $self = shift;
my %children;
# we want controllers with this parent, also controllers
# whos parents are children of this parent, and children
# of those children
no warnings;
my @ctrl = map { $_->id }
grep{ $_->{parent} eq $self->id
or $children{$_->{parent}}
and $children{$_->id}++
} map{ fxn($_)} @{ $self->track->ops };
@ctrl
}
sub sync_one_effect {
my $self = shift;
my $chain = $self->chain;
$this_engine->current_chain($chain);
$this_engine->current_chain_operator($self->ecasound_effect_index);
$self->set(params => get_ecasound_cop_params( scalar @{$self->params} ));
}
sub offset {
my $self = shift;
$fx->{offset}->{$self->chain}
}
sub root_parent {
my $self = shift;
my $parent = $self->parent;
$parent and $parent->parent or $parent or $self
}
sub about {
my $self = shift;
$fx_cache->{registry}->[$self->registry_index]
}
sub track { $ti{$_[0]->chain} }
sub trackname { $_[0]->track->name }
sub ladspa_id {
my $self = shift;
$Audio::Nama::fx_cache->{ladspa_label_to_unique_id}->{$self->type}
}
sub nameline {
my $self = shift;
my @attr_keys = qw(id name surname fxname type ladspa_id);
my %display = map{ $_ => 1 } grep { !/fxname|type|ladspa_id|id/ } @attr_keys;
my %attr = map{ $_ => $self->$_ } @attr_keys ;
my $bypassed = $self->{bypassed} ? " (bypassed)" : '';
not defined $attr{$_} and delete $attr{$_} for @attr_keys;
my $nameline = join qq( ),
map{ $display{$_} ? "$_:$attr{$_}" : $attr{$_} } grep{$attr{$_}} @attr_keys;
$nameline .= "$bypassed\n";
$nameline;
}
sub _effect_index {
my $self = shift;
effect_index($self->type)
}
sub _modify_effect {
my ($self, $parameter, $value, $sign) = @_;
no warnings 'uninitialized';
my $op_id = $self->id;
$parameter--; # convert to zero-based
my $code = $self->type;
my $i = $self->_effect_index;
defined $i or confess "undefined effect code for $op_id: ",Audio::Nama::Dumper $self;
my $parameter_count = scalar @{ $self->about->{params} };
Audio::Nama::pager("$op_id: parameter (", $parameter + 1, ") out of range, skipping.\n"), return
unless ($parameter >= 0 and $parameter < $parameter_count);
Audio::Nama::pager("$op_id: parameter $parameter is read-only, skipping\n"), return
if $self->is_read_only($parameter);
my $new_value;
if ($sign) {
$new_value = eval
( join " ",
$self->params->[$parameter],
$sign,
$value
);
}
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"));
$ui->remove_effect_gui($id);
# recursively remove children
logpkg(__FILE__,__LINE__,'debug',"children found: ". join ",",@$owns) if defined $owns;
map{ remove_effect($_) } @$owns if defined $owns;
;
# remove chain operator
if ( ! $parent ) { remove_op($id) }
# remove controller
else {
remove_op($id);
# remove parent ownership of deleted controller
my $parent_owns = $parent->owns;
logpkg(__FILE__,__LINE__,'debug',"parent $parent owns: ". join ",", @$parent_owns);
@$parent_owns = (grep {$_ ne $id} @$parent_owns);
logpkg(__FILE__,__LINE__,'debug',"parent $parent new owns list: ". join ",", @$parent_owns);
}
# remove effect ID from track
if( my $track = $ti{$n} ){
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};
my $op_index = $self->track_effect_index;
my @children = $self->controllers;
my $count = scalar @children + 1;
my @new_op_list = @{$track->ops};
# remove op and children
my @op_and_ctrl = splice @new_op_list, $op_index, $count;
if ( $pos eq 'ZZZ'){
# put it at the end
push @new_op_list, @op_and_ctrl
}
else {
my $POS = fxn($pos);
my $track2 = $ti{$POS->chain};
Audio::Nama::pager("$pos: position belongs to a different track, skipping.\n"), return
unless $track eq $track2;
my $new_op_index = $POS->track_effect_index;
# insert op
splice @new_op_list, $new_op_index, 0, @op_and_ctrl;
}
# 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");
# if code contains colon, then follow with comma (preset, LADSPA)
# if code contains no colon, then follow with colon (ecasound, ctrl)
$code = '-' . $code . ($code =~ /:/ ? q(,) : q(:) );
my @vals = @{ $self->params };
logpkg(__FILE__,__LINE__,'debug', "values: @vals");
# we start to build iam command
my $add_cmd = $dad ? "ctrl-add " : "cop-add ";
$add_cmd .= $code . join ",", @vals;
# append the -kx operator for a controller-controller
$add_cmd .= " -kx" if $dad and $dad->is_controller;
logpkg(__FILE__,__LINE__,'debug', "command: $add_cmd");
$this_engine->current_chain($chain);
$this_engine->current_chain_operator($dad->ecasound_effect_index) if $dad;
$this_engine->ecasound_iam($add_cmd);
$this_engine->ecasound_iam("cop-bypass on") if $self->bypassed;
my $owns = $self->owns;
(ref $owns) =~ /ARRAY/ or croak "expected array";
logpkg(__FILE__,__LINE__,'debug',"children found: ". join ",", @$owns);
}
#### Effect related routines, some exported, non-OO
# main namespace imports from us, we'll import manually
# to work around dependence issues
sub import_engine_subs {
*ecasound_iam = \&Audio::Nama::ecasound_iam;
*sleeper = \&Audio::Nama::sleeper;
*nama_cmd = \&Audio::Nama::nama_cmd;
*pager = \&Audio::Nama::pager;
*this_op = \&Audio::Nama::this_op;
*this_param = \&Audio::Nama::this_param;
*this_stepsize = \&Audio::Nama::this_stepsize;
}
use Exporter qw(import);
our %EXPORT_TAGS = ( 'all' => [ qw(
effect_index
full_effect_code
lib/Audio/Nama/Effect.pm view on Meta::CPAN
increment_param
decrement_param
set_parameter_value
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = ();
no warnings 'uninitialized'; # needed to avoid confusing test TAP output
sub effect_entry_is_bad {
my $id = shift;
! defined $id
or ! $Audio::Nama::Effect::by_id{$id}
}
# make sure the chain number (track index) is set
sub set_chain_value {
my $p = shift;
return if $p->{chain}; # return if already set
# set chain from track if known
if( $p->{track} )
{
$p->{chain} = $p->{track}->n;
delete $p->{track}
}
# set chain from parent effect if known (add controller)
elsif( $p->{parent_id})
{
$p->{chain} = fxn($p->{parent_id})->chain
}
# set chain from insert target if known (insert effect)
elsif( $p->{before} )
{
$p->{chain} = fxn($p->{before})->chain;
}
#logpkg(__FILE__,__LINE__,'debug',(json_out($p));
}
# How effect chains are added (by default before fader)
# 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
### Track its own add_effect method
### For now this is a single case
die "user effects forbidden on this track"
if $ti{$p->{chain}}
and $ti{$p->{chain}}->forbid_user_ops
and $p->{type} !~ /$config->{latency_op}/;
logpkg(__FILE__,__LINE__,'debug',sub{ "add effect arguments - 1:\n".json_out($p)});
# either insert or add, depending on 'before' setting
my $added = (defined $p->{before} and $p->{before} ne 'ZZZ')
? insert_effect($p)
: append_effect($p);
}
sub append_effect {
my $p = shift;
logsub("&append_effect",Dumper $p);
my %args = %$p;
$args{params} //= [];
my $track = $ti{$args{chain}};
my $add_effects_sub; # we will execute this with engine stopped
my @added;
if( $args{effect_chain})
{
# we will create and apply the effects later
$add_effects_sub = sub{ $args{effect_chain}->add($track)};
}
else
{
# create the effect now, apply it later
# assign defaults if no values supplied
my $count = $fx_cache->{registry}->[effect_index($args{type})]->{count} ;
my @defaults = @{fx_defaults($args{type})};
if( @defaults )
{
for my $i (0..$count - 1)
{
$args{params}[$i] = $defaults[$i]
if ! defined $args{params}[$i] or $args{params}[$i] eq '*'
}
}
my $FX = Audio::Nama::Effect->new(%args);
$args{self} = $FX;
push @added, $FX;
lib/Audio/Nama/Effect.pm view on Meta::CPAN
my %args = %$p;
local $config->{category} = 'ECI_FX';
return(append_effect(\%args)) if $args{before} eq 'ZZZ';
my $running = $this_engine->started();
pager("Cannot insert effect while engine is recording.\n"), return
if $running and Audio::Nama::ChainSetup::really_recording();
pager("Cannot insert effect before controller.\n"), return
if fxn($args{before})->is_controller;
if ($running){
$ui->stop_heartbeat;
Audio::Nama::mute();
$this_engine->stop_command;
sleeper( 0.05);
}
my $pos = fxn($args{before}) or die "$args{before}: effect ID not found";
my $track = $pos->track;
$this_track eq $pos->track or die "$args{before} is not on current track";
#
#logpkg(__FILE__,__LINE__,'debug', $track->name, $/;
#logpkg(__FILE__,__LINE__,'debug', "@{$track->ops}")
my $offset = $pos->track_effect_index;
my $last_index = $#{$track->ops};
# note ops after insertion point
my @after_ops = @{$track->ops}[$offset..$last_index];
# remove corresponding chain operators from the engine
logpkg(__FILE__,__LINE__,'debug',"ops to remove and re-apply: @after_ops");
if ( $this_engine->valid_setup ){
map{ remove_op($_)} reverse @after_ops; # reverse order for correct index
}
# remove the corresponding ids from the track list
splice @{$track->ops}, $offset;
# add the new effect in the proper position
my $added = append_effect(\%args);
logpkg(__FILE__,__LINE__,'debug',"@{$track->ops}");
# replace the effects that had been removed
push @{$track->ops}, @after_ops;
logpkg(__FILE__,__LINE__,'debug',sub{"@{$track->ops}"});
# replace the corresponding Ecasound chain operators
if ($this_engine->valid_setup){
map{ fxn($_)->apply_op } @after_ops;
}
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)
# - abbreviated LADSPA label (something)
# - Ecasound operator (something)
# - abbreviated Ecasound preset (something)
# - Ecasound preset (pn:something)
# - user alias
# there is no interference in these labels at present,
# so we offer the convenience of using them without
# el: and pn: prefixes.
my $input = shift;
my $code;
if ($input !~ /\D/) # i.e. $input is all digits
{
$code = $fx_cache->{ladspa_id_to_label}->{$input};
}
elsif ( $fx_cache->{full_label_to_index}->{$input} )
{
$code = $input
}
else
{
$code = $fx_cache->{partial_label_to_full}->{$input}
}
$code
}
# get integer effect index for Nama effect registry
# e.g. ea => 2
sub effect_index {
my $code = shift;
my $i = $fx_cache->{full_label_to_index}->{full_effect_code($code)};
defined $i or $config->{opts}->{E} or warn("$code: effect index not found\n");
$i
}
sub fx_defaults {
my $code = shift;
my $i = effect_index($code);
my $values = [];
foreach my $p ( @{ $fx_cache->{registry}->[$i]->{params} })
{
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;
# select chain
return unless $this_engine->valid_setup();
$this_engine->current_chain($n);
# deal separately with controllers and chain operators
my $index;
if ( ! $self->is_controller) { # chain operator
logpkg(__FILE__,__LINE__,'debug', "no parent, assuming chain operator");
$index = $self->ecasound_effect_index;
logpkg(__FILE__,__LINE__,'debug', "ops list for chain $n: @{$ti{$n}->ops}");
logpkg(__FILE__,__LINE__,'debug', "operator id to remove: $id");
logpkg(__FILE__,__LINE__,'debug', "ready to remove from chain $n, operator id $id, index $index");
logpkg(__FILE__,__LINE__,'debug',sub{ecasound_iam("cs")});
$this_engine->current_chain_operator($self->ecasound_effect_index);
logpkg(__FILE__,__LINE__,'debug',sub{"selected operator: ". ecasound_iam("cop-selected")});
$this_engine->ecasound_iam("cop-remove");
$this_engine->reset_ecasound_selections_cache();
logpkg(__FILE__,__LINE__,'debug',sub{ecasound_iam("cs")});
} else { # controller
logpkg(__FILE__,__LINE__,'debug', "has parent, assuming controller");
my $ctrl_index = $self->ecasound_controller_index;
logpkg(__FILE__,__LINE__,'debug', ecasound_iam("cs"));
$this_engine->current_chain_operator($self->root_parent->ecasound_controller_index);
logpkg(__FILE__,__LINE__,'debug', "selected operator: ". ecasound_iam("cop-selected"));
$this_engine->current_controller($ctrl_index);
$this_engine->ecasound_iam("ctrl-remove");
logpkg(__FILE__,__LINE__,'debug', ecasound_iam("cs"));
}
}
# Track sax effects: A B C GG HH II D E F
# GG HH and II are controllers applied to chain operator C
#
# to remove controller HH:
#
# for Ecasound, chain op index = 3,
# ctrl index = 2
# = track_effect_index HH - track_effect_index C
#
#
# for Nama, chain op array index 2,
# ctrl arrray index = chain op array index + ctrl_index
# = effect index - 1 + ctrl_index
#
#
## Nama effects
## have a unique ID from capital letters
## IDs are kept in the $track->ops
## Rules for allocating IDs
## new_effect_id() - issues a new ID
## effect_init() - initializes a Nama effect, should be called effect_init()
## add_effect
sub new_effect_id {
# increment $fx->{id_counter} if necessary
# to find an unused effect_id
while( fxn($fx->{id_counter})){ $fx->{id_counter}++};
$fx->{id_counter}
}
## 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
my $chain = $FX->chain;
return unless Audio::Nama::ChainSetup::is_ecasound_chain($chain);
logpkg(__FILE__,__LINE__,'debug', "chain $chain id $id param $param value $val");
# $param is zero-based.
# $FX->params is zero-based.
$this_engine->current_chain($chain);
# update Ecasound's copy of the parameter
if( $FX->is_controller ){
my $i = $FX->ecasound_controller_index;
logpkg(__FILE__,__LINE__,'debug', "controller $id: track: $chain, index: $i, param: $param, value: $val");
$this_engine->current_controller($i);
$this_engine->current_controller_parameter($param);
$this_engine->ecasound_iam("ctrlp-set $val");
}
else { # is operator
my $i = $FX->ecasound_effect_index;
logpkg(__FILE__,__LINE__,'debug', "operator $id: track $chain, index: $i, offset: ". $FX->offset . " param $param, value $val");
$this_engine->current_chain_operator($i);
$this_engine->current_chain_operator_parameter($param);
$this_engine->ecasound_iam("copp-set $val");
}
}
# set both Nama effect and Ecasound chain operator
# 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();
return unless @ops;
my $old_chain = $this_engine->current_chain;
map{ $_->sync_one_effect } grep{ $_ } map{ fxn($_) } @ops;
$this_engine->current_chain($old_chain);
}
sub get_ecasound_cop_params {
local $config->{category} = 'ECI_FX';
my $count = shift;
my @params;
for (1..$count){
$this_engine->current_chain_operator_parameter($_);
push @params, ecasound_iam("copp-get");
}
\@params
}
sub ops_with_controller {
grep{ ! $_->is_controller }
grep{ scalar @{$_->owns} }
map{ fxn($_) }
map{ @{ $_->ops } }
Audio::Nama::ChainSetup::engine_tracks();
}
sub ops_with_read_only_params {
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");
$fx->{offset}->{$chain_id} = $quotes/2 - 1;
}
}
sub expanded_ops_list { # including controllers
# we assume existing ops
my @ops_list = @_;
return () unless @_;
my @expanded = ();
map
{ push @expanded,
$_,
expanded_ops_list( reverse @{fxn($_)->owns} );
# we reverse controllers listing so
# the first controller is applied last
# the insert operation places it adjacent to
# its parent controller
# as a result, the controllers end up
# in the same order as the original
#
# which is convenient for RCS
} @ops_list;
my %seen;
@expanded = grep { ! $seen{$_}++ } @expanded;
}
sub intersect_with_track_ops_list {
my ($track, @effects) = @_;
my %ops;
map{ $ops{$_}++} @{$track->ops};
my @intersection = grep { $ops{$_} } @effects;
my @outersection = grep { !$ops{$_} } @effects;
carp "@outersection: effects don't belong to track: ", $track->name,
". skipping." if @outersection;
@intersection
}
sub bypass_effects {
my($track, @ops) = @_;
set_bypass_state($track, 'on', @ops);
}
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);
my $i = $FX->ecasound_effect_index;
$this_engine->current_chain_operator($i);
$this_engine->ecasound_iam("cop-bypass $bypass_state");
$FX->set(bypassed => ($bypass_state eq 'on') ? 1 : 0);
}
$track->unmute;
}
sub remove_fader_effect {
my ($track, $role) = @_;
remove_effect($track->$role);
delete $track->{$role}
}
# Object interface for effects
sub fxn {
my $id = shift;
$by_id{$id};
}
sub set_current_op {
my $op_id = shift;
my $FX = fxn($op_id);
return unless $FX;
$project->{current_op}->{$FX->trackname} = $op_id;
}
sub set_current_param {
my $parameter = shift;
$project->{current_param}->{Audio::Nama::this_op()} = $parameter;
}
sub set_param_stepsize {
my $stepsize = shift;
$project->{current_stepsize}->{Audio::Nama::this_op()}->[this_param()] = $stepsize;
}
sub increment_param { modify_effect(Audio::Nama::this_op(), this_param(),'+',this_stepsize())}
sub decrement_param { modify_effect(Audio::Nama::this_op(), this_param(),'-',this_stepsize())}
sub set_parameter_value {
my $value = shift;
modify_effect(Audio::Nama::this_op(), this_param(), undef, $value)
}
sub check_fx_consistency {
my $result = {};
my %seen_ids;
my $is_error;
map
{
my $track = $_;
my $name = $track->name;
( run in 0.969 second using v1.01-cache-2.11-cpan-97f6503c9c8 )