Audio-Nama/lib/Audio/Nama/Util.pm
# ----------- Util.pm -----------
# this package is for small subroutines with
# well-defined interfaces
package Audio::Nama::Util;
use v5.36;
our $VERSION = 1.0;
use Carp;
use Data::Dumper::Concise;
use IO::Async::Timer::Periodic;
use IO::Async::Timer::Countdown;
use IO::Async::Loop;
use Audio::Nama::Assign qw(json_out);
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logit logsub logpkg);
no warnings 'uninitialized';
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(
freq
channels
input_node
output_node
signal_format
process_is_running
d1
d2
dn
round
colonize
time_tag
heuristic_time
dest_type
dest_string
create_dir
join_path
wav_off
strip_all
strip_blank_lines
strip_comments
remove_spaces
expand_tilde
resolve_path
dumper
route_output_channels
timer
start_event
stop_event
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = ();
sub freq { [split ',', $_[0] ]->[2] } # e.g. s16_le,2,44100
sub channels { [split ',', $_[0] ]->[1] }
# these are the names of loop devices corresponding
# to pre- and post-fader nodes of a track signal
sub input_node { $_[0].'_in' }
sub output_node {$_[0].'_out'}
sub signal_format {
my ($template, $channel_count) = @_;
$template =~ s/N/$channel_count/;
my $format = $template;
}
sub process_is_running {
my $name = shift;
my @pids = split " ", qx(pgrep $name);
my @ps_ax = grep{ my $pid;
/$name/ and ! /defunct/
and ($pid) = /(\d+)/
and grep{ $pid == $_ } @pids
} split "\n", qx(ps ax) ;
}
sub d1 {
my $n = shift;
sprintf("%.1f", $n)
}
sub d2 {
my $n = shift;
sprintf("%.2f", $n)
}
sub dn {
my ($n, $places) = @_;
sprintf("%." . $places . "f", $n);
}
sub round {
my $n = shift;
return 0 if $n == 0;
$n = int $n if $n > 10;
$n = d2($n) if $n < 10;
$n;
}
sub colonize { # convert seconds to hours:minutes:seconds
my $sec = shift || 0;
my $hours = int ($sec / 3600);
$sec = $sec % 3600;
my $min = int ($sec / 60);
$sec = $sec % 60;
$sec = "0$sec" if $sec < 10;
$min = "0$min" if $min < 10 and $hours;
($hours ? "$hours:" : "") . qq($min:$sec);
}
sub time_tag {
my @time = localtime time;
$time[4]++;
$time[5]+=1900;
@time = @time[5,4,3,2,1,0];
sprintf "%4d.%02d.%02d-%02d:%02d:%02d", @time
}
sub heuristic_time {
my $sec = shift;
d1($sec) . ( $sec > 120 ? " (" . colonize( $sec ) . ") " : " " )
}
sub dest_type {
my $dest = shift;
if($dest eq undef ) { undef }
elsif($dest eq 'bus') { 'bus' }
elsif($dest eq 'null') { 'null' }
elsif($dest eq 'rtnull') { 'rtnull' }
elsif($dest =~ /^loop,/) { 'loop' }
elsif($dest !~ /\D/) { 'soundcard' } # digits only
elsif($dest =~ /^man/) { 'jack_manual' }
elsif($dest eq 'jack') { 'jack_manual' }
elsif($dest =~ /\.ports$/) { 'jack_ports_list' }
elsif( $tn{$dest} ) { 'track' }
else { 'jack_client' }
}
sub dest_string {
my ($type, $id, $width) = @_;
if ($type eq 'soundcard'){
my $ch = $id;
my @channels;
push @channels, $_ for $ch .. ($ch + $width - 1);
'CH '.join '/', @channels
}
else { $id }
}
sub create_dir {
my @dirs = @_;
map{ my $dir = $_;
logpkg(__FILE__,__LINE__,'debug',"creating directory [ $dir ]");
-e $dir
#and (carp "create_dir: '$dir' already exists, skipping...\n")
or system qq( mkdir -p $dir)
} @dirs;
}
sub join_path {
my @parts = @_;
my $path = join '/', @parts;
$path =~ s(/{2,})(/)g;
$path;
}
sub wav_off {
my $wav = shift;
$wav =~ s/\.wav\s*$//i;
$wav;
}
sub strip_all{ strip_trailing_spaces(strip_blank_lines( strip_comments(@_))) }
sub strip_trailing_spaces {
map {s/\s+$//} @_;
@_;
}
sub strip_blank_lines {
map{ s/\n(\s*\n)+/\n/sg } @_;
map{ s/^\n+//s } @_;
@_;
}
sub strip_comments { #
map{ s/#.*$//mg; } @_;
map{ s/\s+$//mg; } @_;
@_
}
sub remove_spaces {
my $entry = shift;
# remove leading and trailing spaces
$entry =~ s/^\s*//;
$entry =~ s/\s*$//;
# convert other spaces to underscores
$entry =~ s/\s+/_/g;
$entry;
}
sub resolve_path {
my $path = shift;
$path = expand_tilde($path);
$path = File::Spec::Link->resolve_all($path);
}
sub expand_tilde {
my $path = shift;
my $home = File::HomeDir->my_home;
# ~bob -> /home/bob
$path =~ s(
^ # beginning of line
~ # tilde
(\w+) # username
)
(File::HomeDir->users_home($1))ex;
# ~/something -> /home/bob/something
$path =~ s(
^ # beginning of line
~ # tilde
/ # slash
)
($home/)x;
$path
}
sub dumper {
! defined $_ and "undef"
or ! (ref $_) and $_
#or (ref $_) =~ /HASH|ARRAY/ and Audio::Nama::json_out($_)
or ref $_ and Dumper($_)
}
sub route_output_channels {
# routes signals (1..$width) to ($dest..$dest+$width-1 )
# returns pairs as arguments to chmove
my ($width, $dest) = @_;
return '' if ! $dest or $dest == 1;
# print "route: width: $width, destination: $dest\n\n";
my $offset = $dest - 1;
my @route;
for my $channel ( map{$width - $_ + 1} 1..$width ) {
push @route,[$channel,($channel + $offset)];
}
@route;
}
my @events = qw(
poll_engine
update_playback_position_display
wraparound
processing_time
remote_control
jack_polling
command_output
);
my %events = map{ $_ => 1} @events;
sub check_event_type ($type) {
confess "illegal event type: $type"
unless $events{$type}
}
sub timer ($delay, $interval, $coderef ) {
my $timer;
if ($interval == 0){
$timer = IO::Async::Timer::Countdown->new(
delay => $delay,
on_expire => $coderef,
);
}
else {
$timer = IO::Async::Timer::Periodic->new(
interval => $interval,
on_tick => $coderef,
);
}
$timer->start;
$text->{loop}->add($timer);
$timer
}
sub stop_event ($label) {
check_event_type($label);
my $event = $Audio::Nama::project->{events}->{$label};
$event->stop if defined $event
}
sub start_event ($label, $async) {
check_event_type($label);
$Audio::Nama::project->{events}->{$label} = $async
}
1;
__END__