Group
Extension

Audio-Nama/lib/Audio/Nama/Track.pm

# ---------- Track -----------
#
package Audio::Nama;
{
package Audio::Nama::Track;
use Role::Tiny::With;
with 'Audio::Nama::Wav',
	 'Audio::Nama::WavModify',
	 'Audio::Nama::TrackRegion',
	 'Audio::Nama::TrackIO',
	 'Audio::Nama::TrackComment',
	 'Audio::Nama::TrackEffect',
	 'Audio::Nama::TrackLatency',
	 'Audio::Nama::TrackWaveform',
	 'Audio::Nama::EffectNickname',
	 'Audio::Nama::BusUtil';
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg logsub);
use Audio::Nama::Effect  qw(fxn);
use List::MoreUtils qw(first_index);
use Try::Tiny;
use v5.36;
our $VERSION = 1.0;
use Carp qw(carp cluck croak);
use File::Copy qw(copy);
use File::Slurp;
use Memoize qw(memoize unmemoize);
no warnings qw(uninitialized redefine);

use Audio::Nama::Util qw(freq input_node dest_type dest_string join_path);
use Audio::Nama::Assign qw(json_out);
use vars qw($n %by_name @by_index %track_names %by_index);
use Audio::Nama::Object qw(
					class 			
					is_mix_track	
					n   			
					name
					group 			
					rw				
					version         
					midi_versions		
					width			
					ops 			
					vol				
					pan				
					fader			
					latency_op		
					offset			
					old_vol_level	
					old_pan_level
					playat			
					region_start	
					region_end
					modifiers		
					looping			
					hide			
					source_id		
					source_type		
					last_source		
					send_id			
					send_type
					target			
					project			
					forbid_user_ops	
					engine_group
					current_edit    

);

# Note that ->vol return the effect_id 
# ->old_volume_level is the level saved before muting
# ->old_pan_level is the level saved before pan full right/left
# commands

initialize();

### class subroutines

sub initialize {
	$n = 0; 	# incrementing numeric key
	%by_index = ();	# return ref to Track by numeric key
	%by_name = ();	# return ref to Track by name
	%track_names = (); 
}

sub idx { # return first free track index
	my $n = 0;
	while (++$n){
		return $n if not $by_index{$n}
	}
}
sub new {
	# returns a reference to an object 
	#
	# tracks are indexed by:
	# (1) name and 
	# (2) by an assigned index that is used as chain_id
	#     the index may be supplied as a parameter
	#
	# 

	my $class = shift;
	my %vals = @_;
	my $novol = delete $vals{novol};
	my $nopan = delete $vals{nopan};
	my $restore = delete $vals{restore};
	say "restoring track $vals{name}" if $restore;
	my @undeclared = grep{ ! $_is_field{$_} } keys %vals;
    croak "undeclared field: @undeclared" if @undeclared;
	
	# silently return if track already exists
	# why not return track? TODO
	
	return if $by_name{$vals{name}};

	my $n = $vals{n} || idx(); 
	my $object = bless { 


		## 		defaults ##
					class	=> $class,
					name 	=> "Audio_$n", 
					group	=> 'Main', 
					n    	=> $n,
					ops     => [],
					width => 1,
					vol  	=> undef,
					pan 	=> undef,
					rw		=> 'MON',

					modifiers 		=> q(), # start, reverse, audioloop, playat
					looping 		=> undef, # do we repeat our sound sample
					source_type 	=> q(soundcard),
					source_id   	=> "1",
					send_type 		=> undef,
					send_id   		=> undef,
					old_vol_level	=> undef,

					@_ 			}, $class;

	$track_names{$vals{name}}++;
	$by_index{$n} = $object;
	$by_name{ $object->name } = $object;
	Audio::Nama::add_pan_control($n) unless $nopan or $restore;
	Audio::Nama::add_volume_control($n) unless $novol or $restore;

	$Audio::Nama::this_track = $object;
	$Audio::Nama::ui->track_gui($object->n) unless $object->hide;
	logpkg(__FILE__,__LINE__,'debug',$object->name, ": ","newly created track",$/,json_out($object->as_hash));
	$object;
}


### object methods

sub snapshot {
	my $track = shift;
	my $fields = shift;
	my %snap; 
	my $i = 0;
	for(@$fields){
		$snap{$_} = $track->$_;
	}
	\%snap;
}


# create an edge representing sound source

# blows up when I move it to TrackIO

sub input_path { 

	my $track = shift;

	# the corresponding bus handles input routing for mix tracks
	# so they don't need to be connected here
	
	return() if $track->is_mixing and ! $track->play;

	# the track may route to:
	# + another track
	# + an external source (soundcard or JACK client)
	# + a WAV file

	if($track->source_type eq 'track'){ ($track->source_id, $track->name) } 

	elsif($track->rec_status =~ /REC|MON/){ 
		(input_node($track->source_type), $track->name) } 

	elsif($track->play and ! $mode->doodle){
		(input_node('wav'), $track->name) 
	}
}

# remove track object and all effects

sub remove {
	my $track = shift;
	my $n = $track->n;
	$ui->remove_track_gui($n); 
	# remove corresponding fades
	map{ $_->remove } grep { $_->track eq $track->name } values %Audio::Nama::Fade::by_index;
	# remove effects
 	map{ Audio::Nama::remove_effect($_) } @{ $track->ops };
 	delete $by_index{$n};
 	delete $by_name{$track->name};
}


# Modified from Object.p to save class
# should this be used in other classes?
sub as_hash {
	my $self = shift;
	my $class = ref $self;
	my %guts = %{ $self };
	$guts{class} = $class; # make sure we save the correct class name
	return \%guts;
}
sub input_object {
	my $track = shift;
	$Audio::Nama::IO::by_name{$track->name}->{input}
}
sub output_object {
	my $track = shift;
	$Audio::Nama::IO::by_name{$track->name}->{output}
}
sub rec_setup_script { 
	my $track = shift;
	join_path(Audio::Nama::project_dir(), $track->name."-rec-setup.sh")
}
sub rec_cleanup_script { 
	my $track = shift;
	join_path(Audio::Nama::project_dir(), $track->name."-rec-cleanup.sh")
}
sub current_edit { $_[0]->{current_edit}//={} }
sub is_mixing {
	my $track = shift;
	$track->is_mixer and ($track->mon or $track->rec)
}
sub bus { $bn{$_[0]->group} }

{ my %system_track = map{ $_, 1} qw( Main Mixdown Eq Low
Mid High Boost midi_record_buffer);
sub is_user_track { ! $system_track{$_[0]->name} }
sub is_system_track { $system_track{$_[0]->name} } 
}

sub engine_group {
	my $track = shift;
	$track->{engine_group} || $Audio::Nama::config->{ecasound_engine_name}
}
sub engine {
	my $track = shift;
	$en{$track->engine_group}
}
sub select_track {
		my $track = shift;
		$Audio::Nama::this_track = $track;
		$this_engine->current_chain( $track->n );
		# XXX wrong engine for MIDI track
		Audio::Nama::set_current_bus();
}
sub is_selected { $Audio::Nama::this_track->name eq $_[0]->name }

sub rec  { $_[0]->rec_status eq REC }
sub mon  { $_[0]->rec_status eq MON }
sub play { $_[0]->rec_status eq PLAY}
sub off  { $_[0]->rec_status eq OFF }

sub current_midi {}
sub fades { grep { $_->{track} eq $_[0]->name } values %Audio::Nama::Fade::by_index  }

} # end package


# subclasses


{
package Audio::Nama::SimpleTrack; # used for Main track
use Audio::Nama::Globals qw(:all);
use v5.36; use Carp; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
use SUPER;
no warnings qw(uninitialized redefine);
our @ISA = 'Audio::Nama::Track';
sub rec_status {
	my $track = shift;
 	$track->rw ne OFF ? MON : OFF 
}
sub destination {
	my $track = shift; 
	return 'Mixdown' if $tn{Mixdown}->rec;
	return $track->SUPER() if $track->rec_status ne OFF
}
#sub rec_status_display { $_[0]->rw ne OFF ? PLAY : OFF }
sub activate_bus {}
}
{
package Audio::Nama::MasteringTrack; # used for mastering chains 
use Audio::Nama::Globals qw(:all);
use v5.36; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
no warnings qw(uninitialized redefine);
our @ISA = 'Audio::Nama::SimpleTrack';

sub rec_status{
	my $track = shift;
 	return OFF if $track->engine_group ne $en{$Audio::Nama::config->{ecasound_engine_name}}->name;
	$mode->mastering ? MON :  OFF;
}
sub source_status {}
sub group_last {0}
sub version {0}
}
{
package Audio::Nama::EarTrack; # for submix helper tracks
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Util qw(dest_string);
use v5.36; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
use SUPER;
no warnings qw(uninitialized redefine);
our @ISA = 'Audio::Nama::SlaveTrack';
sub destination {
	my $track = shift;
	my $bus = $track->bus;
	dest_string($bus->send_type,$bus->send_id, $track->width);
}
sub source_status { $tn{$_[0]->target}->source_status }
sub rec_status { $_[0]->{rw} }
sub width { $_[0]->{width} }
}
{
package Audio::Nama::SlaveTrack;
use Audio::Nama::Globals qw(:all);
use v5.36; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
no warnings qw(uninitialized redefine);
our @ISA = 'Audio::Nama::Track';
sub width { $tn{$_[0]->target}->width }
sub rec_status { $tn{$_[0]->target}->rec_status }
sub full_path { $tn{$_[0]->target}->full_path} 
sub playback_version { $tn{$_[0]->target}->playback_version} 
sub source_type { $tn{$_[0]->target}->source_type}
sub source_id { $tn{$_[0]->target}->source_id}
sub source_status { $tn{$_[0]->target}->source_status }
sub send_type { $tn{$_[0]->target}->send_type }
sub send_id {   $tn{$_[0]->target}->send_id   }

sub dir { $tn{$_[0]->target}->dir }
}
{
package Audio::Nama::BoostTrack; 
use Audio::Nama::Globals qw(:all);
use v5.36; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
no warnings qw(uninitialized redefine);
our @ISA = 'Audio::Nama::Track';
sub rec_status{
	my $track = shift;
	$mode->mastering ? MON :  OFF;
}
sub send_type { $tn{Main}->send_type }
sub send_id { $tn{Main}->send_id }
}
{
package Audio::Nama::CacheRecTrack;
our $VERSION = 1.0;
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg);
our @ISA = qw(Audio::Nama::SlaveTrack);
sub current_version {
	my $track = shift;
	my $target = $tn{$track->target};
	$target->last + 1
}
sub current_wav {
	my $track = shift;
		$tn{$track->target}->name . '_' . $track->current_version . '.wav'
}
sub full_path { my $track = shift; Audio::Nama::join_path( $track->dir, $track->current_wav) }
}
{
package Audio::Nama::MixDownTrack; 
our $VERSION = 1.0;
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg);
use SUPER;
our @ISA = qw(Audio::Nama::Track);
sub current_version {	
	my $track = shift;
	my $last = $track->last;
	my $status = $track->rec_status;
	#logpkg(__FILE__,__LINE__,'debug', "last: $last status: $status");
	if 	($status eq REC){ return ++$last}
	elsif ( $status eq PLAY){ return $track->playback_version } 
	else { return 0 }
}
sub source_status { 
	my $track = shift; 
	return 'Main' if $track->rec;
	my $super = $track->super('source_status');
	$super->($track)
}
sub destination {
	my $track = shift; 
	$tn{Main}->destination if $track->play
}
sub rec_status {
 	my $track = shift;
	$track->rw
# 	return REC if $track->rw eq REC;
# 	Audio::Nama::Track::rec_status($track);
}
sub forbid_user_ops { 1 }
}
{
package Audio::Nama::EditTrack; use Carp qw(carp cluck);
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
our @ISA = 'Audio::Nama::Track';
our $AUTOLOAD;
sub AUTOLOAD {
	my $self = shift;
	logpkg(__FILE__,__LINE__,'debug', $self->name, ": args @_");
    # get tail of method call
    my ($call) = $AUTOLOAD =~ /([^:]+)$/;
	$Audio::Nama::Edit::by_name{$self->name}->$call(@_);
}
sub DESTROY {}
sub current_version {	
	my $track = shift;
	my $last = $track->last;
	my $status = $track->rec_status;
	#logpkg(__FILE__,__LINE__,'debug', "last: $last status: $status");
	if 	($status eq REC){ return ++$last}
	elsif ( $status eq PLAY){ return $track->playback_version } 
	else { return 0 }
}
sub playat_time {
	logpkg(__FILE__,__LINE__,'logcluck',$_[0]->name . "->playat_time");
	$_[0]->play_start_time
}
}
{
package Audio::Nama::VersionTrack;
use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
our @ISA ='Audio::Nama::Track';
sub set_version {}
sub versions { [$_[0]->version] }
}
{
package Audio::Nama::Clip;

# Clips are the units of audio used to 
#  to make sequences. 

# A clip is created from a track. Clips extend the Track
# class in providing a position which derives from the
# object's ordinal position in an array (clips attribute) of
# the parent sequence object.
 
# Clips differ from tracks in that clips
# their one-based position (index) in the sequence items array.
# index is one-based.

use Audio::Nama::Globals qw(:all);
use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
our @ISA = qw( Audio::Nama::VersionTrack Audio::Nama::Track );

sub sequence { my $self = shift; $Audio::Nama::bn{$self->group} };

sub index { my $self = shift; my $i = 0;
	for( @{$self->sequence->items} ){
		$i++;
		return $i if $self->name eq $_
	}
}
sub predecessor {
	my $self = shift;
	$self->sequence->clip($self->index - 1)
}
sub duration {
	my $self = shift;
	$self->{duration} 
		? Audio::Nama::Mark::duration_from_tag($self->{duration})
		: $self->is_region 
			? $self->region_end_time - $self->region_start_time 
			: $self->wav_length;
}
sub endpoint { 
	my $self = shift;
	$self->duration + ( $self->predecessor ?  $self->predecessor->endpoint : 0 )
}
sub playat_time {
	my $self = shift;
	my $previous = $self->predecessor;
	$previous ? $previous->endpoint : 0
}
sub rec_status { $_[0]->version ? PLAY : OFF } 

# we currently are not compatible with offset run mode
# perhaps we can enforce OFF status for clips under 
# offset run mode

} # end package
{ 
package Audio::Nama::Spacer;
our $VERSION = 1.0;
our @ISA = 'Audio::Nama::Clip';
use SUPER;
use Audio::Nama::Object qw(duration);
sub rec_status { OFF }
sub new { 
	my ($class,%args) = @_;

	# remove args we will process
	my $duration = delete $args{duration};

	# give the remainder to the superclass constructor
	@_ = ($class, %args);
	my $self = super();
	#logpkg(__FILE__,__LINE__,'debug',"new object: ", json_out($self->as_hash));
	#logpkg(__FILE__,__LINE__,'debug', "items: ",json_out($items));

	# set the args removed above
	$self->{duration} = $duration;
	$self;
}
} # end package
{ 
package Audio::Nama::WetTrack; # for inserts
use Audio::Nama::Globals qw(:all);
use v5.36; use Audio::Nama::Log qw(logpkg);
our $VERSION = 1.0;
our @ISA = 'Audio::Nama::SlaveTrack';
}

{
package Audio::Nama::MidiTrack; 
use Audio::Nama::Globals qw(:all);
use v5.36;
our $VERSION = 1.0;
use SUPER;
use Audio::Nama::Log qw(logpkg);
our @ISA = qw(Audio::Nama::Track);
sub new {
	my ($class, %args) = @_;
	my $self = super();
	$self
}
# TODO enable
sub mute {   
	my $track = shift;
	if ( $track->exists_midi )
	{
		Audio::Nama::midish_cmd( 'mute '  . $_[0]->current_midi ) 
	}
}
sub unmute { 
	my $track = shift;
	if ( $track->exists_midi )
	{
		# mute unselected versions
		map{ Audio::Nama::midish_cmd( 'mute '. midi_version_name($track->name, $_) ) }
		grep{ $_ != $track->version } @{$track->versions};

		Audio::Nama::midish_cmd( 'unmute '  . $_[0]->current_midi ) 
	}
}
sub rw_set {
	my $track = shift;
	my ($bus, $setting) = @_;
	$track->{rw} = uc $setting;
}
sub exists_midi {
	my $track = shift;
	my ($tlist) = Audio::Nama::midish_cmd('print [tlist]');
	$tlist =~ s/[}{]//g;
	my ($match) = grep{$_ eq $track->current_midi} split " ", $tlist;
}
sub rec_status { 
		my $self = shift;
		if	 ( $self->rw eq REC and	$self->is_selected )							{ REC  } 
		elsif( $self->rw eq REC and	! $self->is_selected )							{ PLAY } 
		elsif( $self->rw eq PLAY )													{ PLAY }
		else																		{ OFF  }
}
sub versions { $_[0]->{midi_versions} }


sub select_track {
		my $track = shift;
		$Audio::Nama::this_track = $track;
		$track->unmute;
		Audio::Nama::set_current_bus();
}
sub current_midi {
	# current MIDI track
	# provides the name of the midish track corresponding to the selected version
	# example: synth_2, for track synth, version 2
	# analagous to current_wav() for audio track which would output synth_2.wav
	 
	my $track = shift;
	
	if 	($track->rec_status eq REC)
	{ 
		midi_version_name($track->name, $track->current_version)
	} 
	elsif ( $track->rec_status eq PLAY)
	{ 
		midi_version_name($track->name, $track->playback_version)
	} 
	else 
	{ 
		logpkg(__FILE__,__LINE__,'debug', "track ", $track->name, ": no current version") ;
		undef; 
	}
}
sub set_io {
	my $track = shift;
	my ($direction, $id) = @_;
	my $type = 'midi';
	
	my $type_field = $direction."_type";
	my $id_field   = $direction."_id";

	# respond to query
	if ( ! $id ){ return $track->$type_field ? $track->$id_field : undef }

	# set values, returning new setting
	$track->set($type_field => $type);
	$track->set($id_field => $id);
} 
sub set_rw {
	my $track = shift;
	my ($bus, $setting) = @_;
	Audio::Nama::throw("can't set MIDI track to MON. Setting is unchanged"), return if $setting eq MON;
	$track->{rw} = $setting;
	# mute all versions
	#$logic{$setting}->($bus, $setting);
}
sub create_midi_version {
	my $track = shift;
	my $n = shift;
	Audio::Nama::add_midi_track(midi_version_name($track->name, $n), hide => 1);
}
sub set_version {
	my ($track, $n) = @_;
	my $name = $track->name;
	if ($n == 0){
		Audio::Nama::pager("$name: version set to zero, following bus default\n");
		$track->set(version => $n)
	} elsif ( grep{ $n == $_ } @{$track->versions} ){
		Audio::Nama::pager("$name: anchoring version $n\n");
		$track->set(version => $n);
	} else { 
		Audio::Nama::throw("$name: version $n does not exist, skipping.\n")
	}
}
sub midi_version {
	my $track = shift;
	join '_', $track->name, $track->version if $track->version
}

}

1;
__END__

Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.