The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# ------------ Graphical User Interface ------------

package Audio::Nama::Graphical;  ## gui routines
use Modern::Perl; use Carp;
our $VERSION = 1.071;
use Audio::Nama::Globals qw($text);

use Module::Load::Conditional qw(can_load);
use Audio::Nama::Assign qw(:all);
use Audio::Nama::Util qw(colonize);
no warnings 'uninitialized';

our @ISA = 'Audio::Nama';      ## default to root class
# widgets

## The following methods belong to the Graphical interface class

sub hello {"make a window";}
sub loop {
	package Audio::Nama;
	$text->{term_attribs}->{already_prompted} = 0;
	$text->{term}->tkRunning(1);
  	while (1) {
  		my ($user_input) = $text->{term}->readline($prompt) ;
  		Audio::Nama::process_line( $user_input );
  	}
}

sub initialize_tk { can_load( modules => { Tk => undef } ) }

# the following graphical methods are placed in the root namespace
# allowing access to root namespace variables 
# with a package path

package Audio::Nama;
# 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("&init_gui");

	init_palettefields(); # keys only


	### 	Tk root window 

	# Tk main window
 	$gui->{mw} = MainWindow->new;  
	get_saved_colors();
	$gui->{mw}->optionAdd('*font', 'Helvetica 12');
	$gui->{mw}->optionAdd('*BorderWidth' => 1);
	$gui->{mw}->title("Ecasound/Nama"); 
	$gui->{mw}->deiconify;

	### init effect window

	$gui->{ew} = $gui->{mw}->Toplevel;
	$gui->{ew}->title("Effect Window");
	$gui->{ew}->deiconify; 
#	$gui->{ew}->withdraw;

	### Exit via Ctrl-C 

	$gui->{mw}->bind('<Control-Key-c>' => sub { exit } ); 
	$gui->{ew}->bind('<Control-Key-c>' => sub { exit } );

    ## Press SPACE to start/stop transport

	$gui->{mw}->bind('<Control-Key- >' => \&toggle_transport); 
	$gui->{ew}->bind('<Control-Key- >' => \&toggle_transport); 
	
	$gui->{canvas} = $gui->{ew}->Scrolled('Canvas')->pack;
	$gui->{canvas}->configure(
		scrollregion =>[2,2,10000,10000],
		-width => 1200,
		-height => 700,	
		);
	$gui->{fx_frame} = $gui->{canvas}->Frame;
	my $id = $gui->{canvas}->createWindow(30,30, -window => $gui->{fx_frame},
											-anchor => 'nw');

	$gui->{project_head} = $gui->{mw}->Label->pack(-fill => 'both');

	$gui->{time_frame} = $gui->{mw}->Frame(
	#	-borderwidth => 20,
	#	-relief => 'groove',
	)->pack(
		-side => 'bottom', 
		-fill => 'both',
	);
	$gui->{mark_frame} = $gui->{time_frame}->Frame->pack(
		-side => 'bottom', 
		-fill => 'both');
	$gui->{seek_frame} = $gui->{time_frame}->Frame->pack(
		-side => 'bottom', 
		-fill => 'both');
	$gui->{transport_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	# $oid_frame = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	$gui->{clock_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	#$gui->{group_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
 	my $track_canvas = $gui->{mw}->Scrolled('Canvas')->pack(-side => 'bottom', -fill => 'both');
 	$track_canvas->configure(
 		-scrollregion =>[2,2,400,9600],
 		-width => 400,
 		-height => 400,	
 		);
	$gui->{track_frame} = $track_canvas->Frame; # ->pack(-fill => 'both');
	#$gui->{track_frame} = $gui->{mw}->Frame;
 	my $id2 = $track_canvas->createWindow(0,0,
		-window => $gui->{track_frame}, 
		-anchor => 'nw');
 	#$gui->{group_label} = $gui->{group_frame}->Menubutton(-text => "GROUP",
 #										-tearoff => 0,
 #										-width => 13)->pack(-side => 'left');
		
	$gui->{add_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	$gui->{perl_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	$gui->{iam_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
	$gui->{load_frame} = $gui->{mw}->Frame->pack(-side => 'bottom', -fill => 'both');
#	my $blank = $gui->{mw}->Label->pack(-side => 'left');



	$gui->{project_label} = $gui->{load_frame}->Label(
		-text => "    Project name: "
	)->pack(-side => 'left');
	$gui->{project_entry} = $gui->{load_frame}->Entry(
		-textvariable => \$gui->{_project_name},
		-width => 25
	)->pack(-side => 'left');

	$gui->{load_project} = $gui->{load_frame}->Button->pack(-side => 'left');;
	$gui->{new_project} = $gui->{load_frame}->Button->pack(-side => 'left');;
	$gui->{quit} = $gui->{load_frame}->Button->pack(-side => 'left');
	$gui->{save_project} = $gui->{load_frame}->Button->pack(-side => 'left');
	$gui->{savefile_entry} = $gui->{load_frame}->Entry(
									-textvariable => \$gui->{_save_id},
									-width => 15
									)->pack(-side => 'left');
	$gui->{load_savefile} = $gui->{load_frame}->Button->pack(-side => 'left');
	$gui->{palette} = $gui->{load_frame}->Menubutton(-tearoff => 0)
		->pack( -side => 'left');
	$gui->{nama_palette} = $gui->{load_frame}->Menubutton(-tearoff => 0)
		->pack( -side => 'left');
	$gui->{add_track}->{label} = $gui->{add_frame}->Label(
		-text => "New track name: ")->pack(-side => 'left');
	$gui->{add_track}->{text_entry} = $gui->{add_frame}->Entry(
		-textvariable => \$gui->{_track_name}, 
		-width => 12
	)->pack(-side => 'left');
	$gui->{add_track}->{rec_label} = $gui->{add_frame}->Label(
		-text => "Input channel or client:"
	)->pack(-side => 'left');
	$gui->{add_track}->{rec_text} = $gui->{add_frame}->Entry(
		-textvariable => \$gui->{_chr}, 
		-width => 10
	)->pack(-side => 'left');
	$gui->{add_track}->{add_mono} = $gui->{add_frame}->Button->pack(-side => 'left');;
	$gui->{add_track}->{add_stereo}  = $gui->{add_frame}->Button->pack(-side => 'left');;

	$gui->{load_project}->configure(
		-text => 'Load',
		-command => sub{ load_project(
			name => remove_spaces($gui->{_project_name}),
			)});
	$gui->{new_project}->configure( 
		-text => 'Create',
		-command => sub{ load_project(
							name => remove_spaces($gui->{_project_name}),
							create => 1)});
	$gui->{save_project}->configure(
		-text => 'Save settings',
		-command => #sub { print "save_id: $gui->{_save_id}\n" });
		 sub {save_state($gui->{_save_id}) });
	$gui->{load_savefile}->configure(
		-text => 'Recall settings',
 		-command => sub {load_project (name => $project->{name},  # current project 
 										settings => $gui->{_save_id})},
				);
	$gui->{quit}->configure(-text => "Quit",
		 -command => sub { 
				return if transport_running();
				save_state($gui->{_save_id});
				pager("Exiting... \n");
				#$text->{term}->tkRunning(0);
				#$gui->{ew}->destroy;
				#$gui->{mw}->destroy;
				#Audio::Nama::process_command('quit');
				exit;
				 });
	$gui->{palette}->configure(
		-text => 'Palette',
		-relief => 'raised',
	);
	$gui->{nama_palette}->configure(
		-text => 'Nama palette',
		-relief => 'raised',
	);

my @color_items = map { [ 'command' => $_, 
							-command  => colorset('mw', $_ ) ]
						} @{$gui->{_palette_fields}};
$gui->{palette}->AddItems( @color_items);

@color_items = map { [ 'command' => $_, 
							-command  => namaset( $_ ) ]
						} @{$gui->{_nama_fields}};

	$gui->{add_track}->{add_mono}->configure( 
			-text => 'Add Mono Track',
			-command => sub { 
					return if $gui->{_track_name} =~ /^\s*$/;	
			add_track(remove_spaces($gui->{_track_name})) }
	);
	$gui->{add_track}->{add_stereo}->configure( 
			-text => 'Add Stereo Track',
			-command => sub { 
								return if $gui->{_track_name} =~ /^\s*$/;	
								add_track(remove_spaces($gui->{_track_name}));
								process_command('stereo');
	});

	my @labels = 
		qw(Track Name Version Status Source Send Volume Mute Unity Pan Center Effects);
	my @widgets;
	map{ push @widgets, $gui->{track_frame}->Label(-text => $_)  } @labels;
	$widgets[0]->grid(@widgets[1..$#widgets]);


}

sub transport_gui {
	my $ui = shift;
	logsub("&transport_gui");

	$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 { 
					stop_transport();
				}
		);
	$gui->{engine_start}->configure(
		-text => "Start",
		-command => sub { 
		return if transport_running();
		my $color = engine_mode_color();
		$ui->project_label_configure(-background => $color);
		start_transport();
				});

#preview_button();
#mastering_button();

}
sub time_gui {
	my $ui = shift;
	logsub("&time_gui");

	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},
		);
	my $length_label = $gui->{clock_frame}->Label(
		-text => 'LENGTH',
		-width => 10,
		);
	$gui->{setup_length} = $gui->{clock_frame}->Label(
	#	-width => 8,
		);

	for my $w ($time_label, $gui->{clock}, $length_label, $gui->{setup_length}) {
		$w->pack(-side => 'left');	
	}

	$gui->{mark_frame} = $gui->{time_frame}->Frame->pack(
		-side => 'bottom', 
		-fill => 'both');
	$gui->{seek_frame} = $gui->{time_frame}->Frame->pack(
		-side => 'bottom', 
		-fill => 'both');
	# jump

	my $jump_label = $gui->{seek_frame}->Label(-text => q(JUMP), -width => 12);
	my @pluses = (1, 5, 10, 30, 60);
	my @minuses = map{ - $_ } reverse @pluses;
	my @fw = map{ my $d = $_; $gui->{seek_frame}->Button(
			-text => $d,
			-command => sub { jump($d) },
			)
		}  @pluses ;
	my @rew = map{ my $d = $_; $gui->{seek_frame}->Button(
			-text => $d,
			-command => sub { jump($d) },
			)
		}  @minuses ;
	my $beg = $gui->{seek_frame}->Button(
			-text => 'Beg',
			-command => \&to_start,
			);
	my $end = $gui->{seek_frame}->Button(
			-text => 'End',
			-command => \&to_end,
			);

	$gui->{seek_unit} = $gui->{seek_frame}->Button( 
			-text => 'Sec',
			);
		for my $w($jump_label, @rew, $beg, $gui->{seek_unit}, $end, @fw){
			$w->pack(-side => 'left')
		}

	$gui->{seek_unit}->configure (-command => sub { &toggle_unit; &show_unit });

	# Marks
	
	my $mark_label = $gui->{mark_frame}->Label(
		-text => q(MARK), 
		-width => 12,
		)->pack(-side => 'left');
		
	my $drop_mark = $gui->{mark_frame}->Button(
		-text => 'Place',
		-command => \&drop_mark,
		)->pack(-side => 'left');	
		
	$gui->{mark_remove} = $gui->{mark_frame}->Button(
		-text => 'Remove',
		-command => \&arm_mark_toggle,
	)->pack(-side => 'left');	

}
sub toggle_unit {
	if ($gui->{_seek_unit} == 1){
		$gui->{_seek_unit} = 60;
		
	} else{ $gui->{_seek_unit} = 1; }
}
sub show_unit { $gui->{seek_unit}->configure(
	-text => ($gui->{_seek_unit} == 1 ? 'Sec' : 'Min') 
)}

sub paint_button {
	my $ui = shift;
	my ($button, $color) = @_;
	$button->configure(-background => $color,
						-activebackground => $color);
}

sub engine_mode_color {
		if ( user_rec_tracks()  ){ 
				$gui->{_nama_palette}->{RecBackground} # live recording 
		} elsif ( Audio::Nama::ChainSetup::really_recording() ){ 
				$gui->{_nama_palette}->{Mixdown}	# mixdown only 
		} elsif ( user_mon_tracks() ){  
				$gui->{_nama_palette}->{Play}; 	# just playback
		} else { $gui->{_old_bg} } 
}
sub user_rec_tracks { some_user_tracks(REC) }
sub user_mon_tracks { some_user_tracks(PLAY) }

sub some_user_tracks {
	my $which = shift;
	my @user_tracks = Audio::Nama::audio_tracks();
	splice @user_tracks, 0, 2; # drop Master and Mixdown tracks
	return unless @user_tracks;
	my @selected_user_tracks = grep { $_->rec_status eq $which } @user_tracks;
	return unless @selected_user_tracks;
	map{ $_->n } @selected_user_tracks;
}

sub flash_ready {

	my $color = engine_mode_color();
	logpkg(__FILE__,__LINE__,'debug', "flash color: $color");
	$ui->length_display(-background => $color);
	$ui->project_label_configure(-background => $color) unless $mode->{preview};
 	$project->{events}->{heartbeat} = AE::timer(5, 0, \&reset_engine_mode_color_display);
}
sub reset_engine_mode_color_display { $ui->project_label_configure(
	-background => $gui->{_nama_palette}->{OffBackground} )
}
sub set_engine_mode_color_display { $ui->project_label_configure(-background => engine_mode_color()) }
sub group_gui {  
	my $ui = shift;
	my $group = $bn{Main}; 
	my $dummy = $gui->{track_frame}->Label(-text => ' '); 
	$gui->{group_label} = 	$gui->{track_frame}->Label(
			-text => "G R O U P",
			-foreground => $gui->{_nama_palette}->{GroupForeground},
			-background => $gui->{_nama_palette}->{GroupBackground},

 );
	$gui->{group_version} = $gui->{track_frame}->Menubutton( 
		-text => q( ), 
		-tearoff => 0,
		-foreground => $gui->{_nama_palette}->{GroupForeground},
		-background => $gui->{_nama_palette}->{GroupBackground},
);
	$gui->{group_rw} = $gui->{track_frame}->Menubutton( 
		-text    => $group->rw,
	 	-tearoff => 0,
		-foreground => $gui->{_nama_palette}->{GroupForeground},
		-background => $gui->{_nama_palette}->{GroupBackground},
);


		
		$gui->{group_rw}->AddItems([
			'command' => MON,
			-background => $gui->{_old_bg},
			-command => sub { 
				return if Audio::Nama::eval_iam("engine-status") eq 'running';
				$group->set(rw => MON);
				$gui->{group_rw}->configure(-text => MON);
				refresh();
				Audio::Nama::reconfigure_engine()
				}
			],[
			'command' => OFF,
			-background => $gui->{_old_bg},
			-command => sub { 
				return if Audio::Nama::eval_iam("engine-status") eq 'running';
				$group->set(rw => OFF);
				$gui->{group_rw}->configure(-text => OFF);
				refresh();
				Audio::Nama::reconfigure_engine()
				}
			]);
			$dummy->grid($gui->{group_label}, $gui->{group_version}, $gui->{group_rw});
			#$ui->global_version_buttons;

}
sub global_version_buttons {
	my $version = $gui->{group_version};
	$version and map { $_->destroy } $version->children;
		
	logpkg(__FILE__,__LINE__,'debug', "making global version buttons range: " ,
		join ' ',1..$bn{Main}->last);

			$version->radiobutton( 

				-label => (''),
				-value => 0,
				-command => sub { 
					$bn{Main}->set(version => 0); 
					$version->configure(-text => " ");
					Audio::Nama::reconfigure_engine();
					refresh();
					}
			);

 	for my $v (1..$bn{Main}->last) { 

	# the highest version number of all tracks in the
	# $bn{Main} group
	
	my @user_track_indices = grep { $_ > 2 } map {$_->n} Audio::Nama::audio_tracks();
	
		next unless grep{  grep{ $v == $_ } @{ $ti{$_}->versions } }
			@user_track_indices;
		

			$version->radiobutton( 

				-label => ($v ? $v : ''),
				-value => $v,
				-command => sub { 
					$bn{Main}->set(version => $v); 
					$version->configure(-text => $v);
					Audio::Nama::reconfigure_engine();
					refresh();
					}

			);
 	}
}
sub track_gui { 
	logsub("&track_gui");
	my $ui = shift;
	my $n = shift;
	return if $ti{$n}->hide;
	
	logpkg(__FILE__,__LINE__,'debug', "found index: $n");
	my @rw_items = @_ ? @_ : (
			[ 'command' => "REC",
				-foreground => 'red',
				-command  => sub { 
					return if Audio::Nama::eval_iam("engine-status") eq 'running';
					$ti{$n}->set(rw => "REC");
					
					$ui->refresh_track($n);
					#refresh_group();
					Audio::Nama::reconfigure_engine();
			}],
			[ 'command' => "PLAY",
				-command  => sub { 
					return if Audio::Nama::eval_iam("engine-status") eq 'running';
					$ti{$n}->set(rw => "PLAY");
					$ui->refresh_track($n);
					#refresh_group();
					Audio::Nama::reconfigure_engine();
			}],
			[ 'command' => "MON",
				-foreground => 'red',
				-command  => sub { 
					return if Audio::Nama::eval_iam("engine-status") eq 'running';
					$ti{$n}->set(rw => "MON");
					
					$ui->refresh_track($n);
					#refresh_group();
					Audio::Nama::reconfigure_engine();
			}],
			[ 'command' => "OFF", 
				-command  => sub { 
					return if Audio::Nama::eval_iam("engine-status") eq 'running';
					$ti{$n}->set(rw => "OFF");
					$ui->refresh_track($n);
					#refresh_group();
					Audio::Nama::reconfigure_engine();
			}],
		);
	my ($number, $name, $version, $rw, $ch_r, $ch_m, $vol, $mute, $solo, $unity, $pan, $center);
	$number = $gui->{track_frame}->Label(-text => $n,
									-justify => 'left');
	my $stub = " ";
	$stub .= $ti{$n}->version;
	$name = $gui->{track_frame}->Label(
			-text => $ti{$n}->name,
			-justify => 'left');
	$version = $gui->{track_frame}->Menubutton( 
					-text => $stub,
					# -relief => 'sunken',
					-tearoff => 0);
	my @versions = '';
	#push @versions, @{$ti{$n}->versions} if @{$ti{$n}->versions};
	my $ref = ref $ti{$n}->versions ;
		$ref =~ /ARRAY/ and 
		push (@versions, @{$ti{$n}->versions}) or
		croak "chain $n, found unexpectedly $ref\n";;
	my $indicator;
	for my $v (@versions) {
					$version->radiobutton(
						-label => $v,
						-value => $v,
						-variable => \$indicator,
						-command => 
		sub { 
			$ti{$n}->set( version => $v );
			return if $ti{$n}->rec_status eq "REC";
			$version->configure( -text=> $ti{$n}->current_version );
			Audio::Nama::reconfigure_engine();
			}
					);
	}

	$ch_r = $gui->{track_frame}->Menubutton(
					# -relief => 'groove',
					-tearoff => 0,
				);
	my @range;
	push @range, 1..$config->{soundcard_channels} if $n > 2; # exclude Master/Mixdown
	
	for my $v (@range) {
		$ch_r->radiobutton(
			-label => $v,
			-value => $v,
			-command => sub { 
				return if Audio::Nama::eval_iam("engine-status") eq 'running';
			#	$ti{$n}->set(rw => REC);
				$ti{$n}->source($v);
				$ui->refresh_track($n) }
			)
	}
	@range = ();

	push @range, "off" if $n > 2;
	push @range, 1..$config->{soundcard_channels} if $n != 2; # exclude Mixdown

	$ch_m = $gui->{track_frame}->Menubutton(
					-tearoff => 0,
					# -relief => 'groove',
				);
				for my $v (@range) {
					$ch_m->radiobutton(
						-label => $v,
						-value => $v,
						-command => sub { 
							return if Audio::Nama::eval_iam("engine-status") eq 'running';
							local $this_track = $ti{$n};
							if( $v eq 'off' )
								 { process_command('nosend') }
							else { $this_track->set_send($v) }
							$ui->refresh_track($n);
							Audio::Nama::reconfigure_engine();
 						}
				 		)
				}
	$rw = $gui->{track_frame}->Menubutton(
		-text => $ti{$n}->rw,
		-tearoff => 0,
		# -relief => 'groove',
	);
	map{$rw->AddItems($_)} @rw_items; 

 
	my $p_num = 0; # needed when using parameter controllers
	# Volume
	
	if ( Audio::Nama::need_vol_pan($ti{$n}->name, "vol") ){

		my $vol_id = $ti{$n}->vol;

		logpkg(__FILE__,__LINE__,'debug', "vol effect_id: $vol_id");
		my %p = ( 	parent => \$gui->{track_frame},
				chain  => $n,
				type => 'ea',
				id => $vol_id,
				p_num		=> $p_num,
				length => 300, 
				);


		 logpkg(__FILE__,__LINE__,'debug',sub{my %q = %p; delete $q{parent}; print
		 "=============\n%p\n",json_out(\%q)});

		$vol = make_scale ( \%p );
		# Mute

		$mute = $gui->{track_frame}->Button(
			-command => sub { 
				my $FX = fxn($vol_id);
				if (	$FX->params->[0] != $config->{mute_level}->{$FX->type} 
					and $FX->params->[0] != $config->{fade_out_level}->{$FX->type} 
				) {  # non-zero volume
					$ti{$n}->mute;
					$mute->configure(-background => $gui->{_nama_palette}->{Mute});
				}
				else {
					$ti{$n}->unmute;
					$mute->configure(-background => $gui->{_nama_palette}->{OffBackground})
				}
			}	
		  );

		# Unity

		$unity = $gui->{track_frame}->Button(
				-command => sub { 
					my $FX = fxn($vol_id);
					Audio::Nama::update_effect(
						$vol_id, 
						0, 
						$config->{unity_level}->{$FX->type});
				}
		  );
	} else {

		$vol = $gui->{track_frame}->Label;
		$mute = $gui->{track_frame}->Label;
		$unity = $gui->{track_frame}->Label;

	}

	if ( Audio::Nama::need_vol_pan($ti{$n}->name, "pan") ){
	  
		# Pan
		
		my $pan_id = $ti{$n}->pan;
		
		logpkg(__FILE__,__LINE__,'debug', "pan effect_id: $pan_id");
		$p_num = 0;           # first parameter
		my %q = ( 	parent => \$gui->{track_frame},
				chain  => $n,
				type => 'epp',
				id => $pan_id,
				p_num		=> $p_num,
				);
		# logpkg(__FILE__,__LINE__,'debug',sub{ my %q = %p; delete $q{parent}; print "x=============\n%p\n",json_out(\%q) });
		$pan = make_scale ( \%q );

		# Center

		$center = $gui->{track_frame}->Button(
			-command => sub { 
				Audio::Nama::update_effect($pan_id, 0, 50);
			}
		  );
	} else { 

		$pan = $gui->{track_frame}->Label;
		$center = $gui->{track_frame}->Label;
	}
	
	my $effects = $gui->{fx_frame}->Frame->pack(-fill => 'both');;

	# effects, held by track_widget->n->effects is the frame for
	# all effects of the track

	@{ $gui->{tracks}->{$n} }{qw(name version rw ch_r ch_m mute effects)} 
		= ($name,  $version, $rw, $ch_r, $ch_m, $mute, \$effects);#a ref to the object
	#logpkg(__FILE__,__LINE__,'debug', "=============$gui->{tracks}\n",sub{json_out($gui->{tracks})});
	my $independent_effects_frame 
		= ${ $gui->{tracks}->{$n}->{effects} }->Frame->pack(-fill => 'x');


	my $controllers_frame 
		= ${ $gui->{tracks}->{$n}->{effects} }->Frame->pack(-fill => 'x');
	
	# parents are the independent effects
	# children are controllers for various paramters

	$gui->{tracks}->{$n}->{parents} = $independent_effects_frame;

	$gui->{tracks}->{$n}->{children} = $controllers_frame;
	
	$independent_effects_frame
		->Label(-text => uc $ti{$n}->name )->pack(-side => 'left');

	#logpkg(__FILE__,__LINE__,'debug',"Number: $n"),MainLoop if $n == 2;
	my @tags = qw( EF P1 P2 L1 L2 L3 L4 );
	my @starts =   ( $fx_cache->{split}->{cop}{a}, 
					 $fx_cache->{split}->{preset}{a}, 
					 $fx_cache->{split}->{preset}{b}, 
					 $fx_cache->{split}->{ladspa}{a}, 
					 $fx_cache->{split}->{ladspa}{b}, 
					 $fx_cache->{split}->{ladspa}{c}, 
					 $fx_cache->{split}->{ladspa}{d}, 
					);
	my @ends   =   ( $fx_cache->{split}->{cop}{z}, 
					 $fx_cache->{split}->{preset}{b}, 
					 $fx_cache->{split}->{preset}{z}, 
					 $fx_cache->{split}->{ladspa}{b}-1, 
					 $fx_cache->{split}->{ladspa}{c}-1, 
					 $fx_cache->{split}->{ladspa}{d}-1, 
					 $fx_cache->{split}->{ladspa}{z}, 
					);
	my @add_effect;

	map{push @add_effect, effect_button($n, shift @tags, shift @starts, shift @ends)} 1..@tags;
	
	$number->grid($name, $version, $rw, $ch_r, $ch_m, $vol, $mute, $unity, $pan, $center, @add_effect);

	$gui->{tracks_remove}->{$n} = [
		grep{ $_ } (
			$number, 
			$name, 
			$version, 
			$rw, 
			$ch_r, 
			$ch_m, 
			$vol,
			$mute, 
			$unity, 
			$pan, 
			$center, 
			@add_effect,
			$effects,
		)
	];

	$ui->refresh_track($n);

}

sub remove_track_gui {
 	my $ui = shift;
 	my $n = shift;
	logsub("&remove_track_gui");
	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("&create_master_and_mix_tracks");


	my @rw_items = (
			[ 'command' => "MON",
				-command  => sub { 
						return if Audio::Nama::eval_iam("engine-status") eq 'running';
						$tn{Master}->set(rw => "MON");
						$ui->refresh_track($tn{Master}->n);
			}],
			[ 'command' => "OFF", 
				-command  => sub { 
						return if Audio::Nama::eval_iam("engine-status") eq 'running';
						$tn{Master}->set(rw => "OFF");
						$ui->refresh_track($tn{Master}->n);
			}],
		);

	$ui->track_gui( $tn{Master}->n, @rw_items );

	$ui->track_gui( $tn{Mixdown}->n); 

	#$ui->group_gui('Main');
}

sub update_version_button {
	my $ui = shift;
	my ($n, $v) = @_;
	carp ("no version provided \n") if ! $v;
	my $w = $gui->{tracks}->{$n}->{version};
					$w->radiobutton(
						-label => $v,
						-value => $v,
						-command => 
		sub { $gui->{tracks}->{$n}->{version}->configure(-text=>$v) 
				unless $ti{$n}->rec_status eq "REC" }
					);
}

sub add_effect_gui {
		logsub("&add_effect_gui");
		my $ui = shift;
		my %p 			= %{shift()};
		my ($n,$code,$id,$parent,$parameter) =
			@p{qw(chain type id parent parameter)};
		my $i = $fx_cache->{full_label_to_index}->{$code};
		my $FX = fxn($id);

		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
		# existing id if supplied

		# check display format, may be 'scale' 'field' or 'hidden'
		
		my $display_type = $FX->display; # individual setting
		defined $display_type or $display_type = $fx_cache->{registry}->[$i]->{display}; # template
		logpkg(__FILE__,__LINE__,'debug', "display type: $display_type");

		return if $display_type eq q(hidden);

		my $frame ;
		if ( ! $parent ){ # independent effect
			$frame = $gui->{tracks}->{$n}->{parents}->Frame->pack(
				-side => 'left', 
				-anchor => 'nw',)
		} else {                 # controller
			$frame = $gui->{tracks}->{$n}->{children}->Frame->pack(
				-side => 'top', 
				-anchor => 'nw')
		}

		$gui->{fx}->{$id} = $frame; 
		# we need a separate frame so title can be long

		# here add menu items for Add Controller, and Remove

		my $parentage = $fx_cache->{registry}->[
			$fx_cache->{full_label_to_index}->{$FX->type} 
		]->{name};
		$parentage and $parentage .=  " - ";
		logpkg(__FILE__,__LINE__,'debug', "parentage: $parentage");
		my $eff = $frame->Menubutton(
			-text => $parentage. $fx_cache->{registry}->[$i]->{name}, -tearoff => 0,);

		$eff->AddItems([
			'command' => "Remove",
			-command => sub { remove_effect($id) }
		]);
		$eff->grid();
		my @labels;
		my @sliders;

		# make widgets

		for my $p (0..$fx_cache->{registry}->[$i]->{count} - 1 ) {
		my @items;
		#logpkg(__FILE__,__LINE__,'debug', "p_first: $p_first, p_last: $p_last");
		for my $j ($fx_cache->{split}->{ctrl}{a}..$fx_cache->{split}->{ctrl}{z}) {   
			push @items, 				
				[ 'command' => $fx_cache->{registry}->[$j]->{name},
					-command => sub { add_effect({
							parent 	=> $id,
							chain 	=> $n,
							params 	=> [ $p  + 1 ],
							type 	=> $fx_cache->{registry}->[$j]->{code} })  }
				];

		}
		push @labels, $frame->Menubutton(
				-text => $fx_cache->{registry}->[$i]->{params}->[$p]->{name},
				-menuitems => [@items],
				-tearoff => 0,
		);
			logpkg(__FILE__,__LINE__,'debug', "parameter name: ",
				$fx_cache->{registry}->[$i]->{params}->[$p]->{name});
			my $v =  # for argument vector 
			{	parent => \$frame,
				id => $id, 
				p_num  => $p,
			};
			push @sliders,make_scale($v);
		}

		if (@sliders) {

			$sliders[0]->grid(@sliders[1..$#sliders]);
			 $labels[0]->grid(@labels[1..$#labels]);
		}
}


sub project_label_configure{ 
	my $ui = shift;
	$gui->{project_head}->configure( @_ ) }

sub length_display{ 
	my $ui = shift;
	$gui->{setup_length}->configure(@_)};

sub clock_config { 
	my $ui = shift;
	$gui->{clock}->configure( @_ )}

sub manifest { $gui->{ew}->deiconify() }

sub destroy_widgets {

	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("&remove_effect_gui");
	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("&effect_button");
	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");
	}
		
		for my $j (@indices) { 
		push @items, 				
			[ 'command' => "$fx_cache->{registry}->[$j]->{count} $fx_cache->{registry}->[$j]->{name}" ,
				-command  => sub { 
					 add_effect( {chain => $n, type => $fx_cache->{registry}->[$j]->{code} } ); 
					$gui->{ew}->deiconify; # display effects window
					} 
			];
	}
	$widget = $gui->{track_frame}->Menubutton(
		-text => $label,
		-tearoff =>0,
		# -relief => 'raised',
		-menuitems => [@items],
	);
	$widget;
}

sub make_scale {
	
	logsub("&make_scale");
	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);
	my $n = $FX->chain;
	my $code = $FX->type;
	my $p  = $p{p_num};
	my $i  = $fx_cache->{full_label_to_index}->{$code};

	logpkg(__FILE__,__LINE__,'debug', "id: $id code: $code");
	

	# check display format, may be text-field or hidden,

	logpkg(__FILE__,__LINE__,'debug',"i: $i code: $fx_cache->{registry}->[$i]->{code} display: $fx_cache->{registry}->[$i]->{display}");
	my $display_type = $FX->display;
	defined $display_type or $display_type = $fx_cache->{registry}->[$i]->{display};
	logpkg(__FILE__,__LINE__,'debug', "display type: $display_type");
	return if $display_type eq q(hidden);


	logpkg(__FILE__,__LINE__,'debug', "to: ", $fx_cache->{registry}->[$i]->{params}->[$p]->{end}) ;
	logpkg(__FILE__,__LINE__,'debug', "p: $p code: $code");
	logpkg(__FILE__,__LINE__,'debug', "is_log_scale: ".is_log_scale($i,$p));

	# set display type to individually specified value if it exists
	# otherwise to the default for the controller class


	
	if 	($display_type eq q(scale) ) { 

		# return scale type controller widgets
		my $frame = ${ $p{parent} }->Frame;
			

		#return ${ $p{parent} }->Scale(
		
		my $log_display;
		
		my $controller = $frame->Scale(
			-variable => \$FX->{params}->[$p],
			-orient => 'horizontal',
			-from   =>  $fx_cache->{registry}->[$i]->{params}->[$p]->{begin},
			-to     =>  $fx_cache->{registry}->[$i]->{params}->[$p]->{end},
			-resolution => resolution($i, $p),
		  -width => 12,
		  -length => $p{length} ? $p{length} : 100,
		  -command => sub { Audio::Nama::_update_effect($id, $p, $FX->params->[$p]) },
			-state => $FX->is_read_only($p) ? 'disabled' : 'normal',
		  );

		# auxiliary field for logarithmic display
		if ( is_log_scale($i, $p)  )
		#	or $code eq 'ea') 
			{
			my $log_display = $frame->Label(
				-text => exp $fx_cache->{registry}->[$i]->{params}->[$p]->{default},
				-width => 5,
				);
			$controller->configure(
				-variable => \$FX->{params_log}->[$p],
		  		-command => sub { 
					$FX->params->[$p] = exp $FX->params_log->[$p];
					Audio::Nama::_update_effect($id, $p, $FX->params->[$p]);
					$log_display->configure(
						-text => 
						$fx_cache->{registry}->[$i]->{params}->[$p]->{name} =~ /hz|frequency/i
							? int $FX->params->[$p]
							: dn($FX->params->[$p], 1)
						);
					}
				);
		$log_display->grid($controller);
		}
		else { $controller->grid; }

		return $frame;

	}	

	elsif ($display_type eq q(field) ){ 

	 	# then return field type controller widget

		return ${ $p{parent} }->Entry(
			-textvariable =>\$FX->params->[$p],
			-width => 6,
	#		-command => sub { Audio::Nama::_update_effect($id, $p, $FX->params->[$p]) },
			# doesn't work with Entry widget
			);	

	}
	else { croak "missing or unexpected display type: $display_type" }

}

sub is_log_scale {
	my ($i, $p) = @_;
	$fx_cache->{registry}->[$i]->{params}->[$p]->{hint} =~ /logarithm/ 
}
sub resolution {
	my ($i, $p) = @_;
	my $res = $fx_cache->{registry}->[$i]->{params}->[$p]->{resolution};
	return $res if $res;
	my $end = $fx_cache->{registry}->[$i]->{params}->[$p]->{end};
	my $beg = $fx_cache->{registry}->[$i]->{params}->[$p]->{begin};
	return 1 if abs($end - $beg) > 30;
	return abs($end - $beg)/100
}

sub arm_mark_toggle { 
	if ($gui->{_markers_armed}) {
		$gui->{_markers_armed} = 0;
		$gui->{mark_remove}->configure( -background => $gui->{_nama_palette}->{OffBackground});
	 } else{
		$gui->{_markers_armed} = 1;
		$gui->{mark_remove}->configure( -background => $gui->{_nama_palette}->{MarkArmed});
	}
}
sub marker {
	my $ui = shift;
	my $mark = shift; # Mark
	logpkg(__FILE__,__LINE__,'debug',"mark is ", ref $mark);
	my $pos = $mark->time;
	logpkg(__FILE__,__LINE__,'debug',$pos, " ", int $pos);
		$gui->{marks}->{$pos} = $gui->{mark_frame}->Button( 
			-text => (join " ",  colonize( int $pos ), $mark->name),
			-background => $gui->{_nama_palette}->{OffBackground},
			-command => sub { Audio::Nama::mark($mark) },
		)->pack(-side => 'left');
}

sub restore_time_marks {
	my $ui = shift;
	map{ $ui->marker($_) } Audio::Nama::Mark::all() ; 
	$gui->{seek_unit}->configure( -text => $gui->{_seek_unit} == 1 ? q(Sec) : q(Min) )
}
sub destroy_marker {
	my $ui = shift;
	my $pos = shift;
	$gui->{marks}->{$pos}->destroy; 
}


sub get_saved_colors {
	logsub("&get_saved_colors");

	# 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;
	$pal .= '.json' unless $pal =~ /\.json$/;
	pager("pal $pal");
	$pal = -f $pal 
			? scalar read_file($pal)
			: get_data_section('default_palette_json');
	my $ref = decode($pal, 'json');
	#say "palette file",json_out($ref);

	assign_singletons({ data => $ref });
	
	$gui->{_old_abg} = $gui->{_palette}->{mw}{activeBackground};
	$gui->{_old_abg} = $gui->{project_head}->cget('-activebackground') unless $gui->{_old_abg};
	#print "1palette: \n", json_out( $gui->{_palette} );
	#print "\n1namapalette: \n", json_out($gui->{_nama_palette});
	my %setformat;
	map{ $setformat{$_} = $gui->{_palette}->{mw}{$_} if $gui->{_palette}->{mw}{$_}  } 
		keys %{$gui->{_palette}->{mw}};	
	#print "\nsetformat: \n", json_out(\%setformat);
	$gui->{mw}->setPalette( %setformat );
}
sub colorset {
	my ($widgetid, $field) = @_;
	sub { 
			my $widget = $gui->{$widgetid};
			#print "ancestor: $widgetid\n";
			my $new_color = colorchooser($field,$widget->cget("-$field"));
			if( defined $new_color ){
				
				# install color in palette listing
				$gui->{_palette}->{$widgetid}{$field} = $new_color;

				# set the color
				my @fields =  ($field => $new_color);
				push (@fields, 'background', $widget->cget('-background'))
					unless $field eq 'background';
				#print "fields: @fields\n";
				$widget->setPalette( @fields );
			}
 	};
}

sub namaset {
	my ($field) = @_;
	sub { 	
			#print "f: $field np: $gui->{_nama_palette}->{$field}\n";
			my $color = colorchooser($field,$gui->{_nama_palette}->{$field});
			if ($color){ 
				# install color in palette listing
				$gui->{_nama_palette}->{$field} = $color;

				# set those objects who are not
				# handled by refresh
	*rec = \$gui->{_nama_palette}->{RecBackground};
	*mon = \$gui->{_nama_palette}->{MonBackground};
	*off = \$gui->{_nama_palette}->{OffBackground};

				$gui->{clock}->configure(
					-background => $gui->{_nama_palette}->{ClockBackground},
					-foreground => $gui->{_nama_palette}->{ClockForeground},
				);
				$gui->{group_label}->configure(
					-background => $gui->{_nama_palette}->{GroupBackground},
					-foreground => $gui->{_nama_palette}->{GroupForeground},
				);
				refresh();
			}
	}

}

sub colorchooser { 
	logsub("&colorchooser");
	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 {
	@{$gui->{_palette_fields}} = qw[ 
		foreground
		background
		activeForeground
		activeBackground
		selectForeground
		selectBackground
		selectColor
		highlightColor
		highlightBackground
		disabledForeground
		insertBackground
		troughColor
	];

	@{$gui->{_nama_fields}} = qw [
		RecForeground
		RecBackground
		MonForeground
		MonBackground
		OffForeground
		OffBackground
		ClockForeground
		ClockBackground
		Capture
		Play
		Mixdown
		GroupForeground
		GroupBackground
		SendForeground
		SendBackground
		SourceForeground
		SourceBackground
		Mute
		MarkArmed
	];
}

sub save_palette {
 	serialize (
 		file => $file->gui_palette,
		format => 'json',
 		vars => [ qw( $gui->{_palette} $gui->{_nama_palette} ) ],
 		class => 'Audio::Nama')
}

### end



## refresh functions

sub set_widget_color {
	my ($widget, $status) = @_;
	my %rw_foreground = (	REC  => $gui->{_nama_palette}->{RecForeground},
						 	PLAY => $gui->{_nama_palette}->{MonForeground},
						 	MON => $gui->{_nama_palette}->{MonForeground},
						 	OFF => $gui->{_nama_palette}->{OffForeground},
						);

	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("&refresh_group");
	
	
		my $status;
		if ( 	grep{ $_->rec_status eq REC} 
				map{ $tn{$_} }
				$bn{Main}->tracks ){

			$status = REC

		}elsif(	grep{ $_->rec_status eq PLAY} 
				map{ $tn{$_} }
				$bn{Main}->tracks ){

			$status = PLAY

		}else{ 
		
			$status = OFF }

logit(__LINE__,'Audio::Nama::Refresh','debug', "group status: $status");

	set_widget_color($gui->{group_rw}, $status); 



	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("&refresh_track");
	
	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 => 
				$n > 2
					? $ti{$n}->source
					:  q() );
	$gui->{tracks}->{$n}->{ch_m}->configure( -text => $ti{$n}->send);
	$gui->{tracks}->{$n}->{version}->configure(-text => $ti{$n}->current_version || "");
	
	map{ set_widget_color( 	$gui->{tracks}->{$n}->{$_}, 
							$rec_status)
	} qw(name rw );
	
	set_widget_color( 	$gui->{tracks}->{$n}->{ch_r},
				
 							($rec_status eq REC
								and $n > 2 )
 								? REC
 								: OFF);
	
	set_widget_color( $gui->{tracks}->{$n}->{ch_m},
							$rec_status eq OFF 
								? OFF
								: $ti{$n}->send 
									? MON
									: OFF);
}

sub refresh {  
	Audio::Nama::remove_riff_header_stubs();
 	#$ui->refresh_group(); 
	#map{ $ui->refresh_track($_) } map{$_->n} grep{!  $_->hide} Audio::Nama::audio_tracks();
	#map{ $ui->refresh_track($_) } grep{$remove_track_widget{$_} map{$_->n}  Audio::Nama::audio_tracks();
	map{ $ui->refresh_track($_) } map{$_->n}  Audio::Nama::audio_tracks();
}
### end


1;

__END__