Source

App::Harmonograph / bin / harmonograph.pl

Full commit
#!/usr/bin/perl
use v5.12;
use warnings;

package Harmonograph;
our $VERSION = '0.75';
use base qw(Wx::App);
use Wx qw/ :everything /;
use Math::Trig;
my %app;
my $maltafelgroesse = 450;
my $midoffset = $maltafelgroesse / 2;
my $amp = 200;
my @label = qw(X Y X Y Betrag Reibung Dauer Zoom Farbe);
my @name = qw(freqx freqy ampx ampy rotation friction length zoom color);
my @ranges = (
	[1,1,27],[1,1,27], [0,$amp, $amp*2], [0,$amp, $amp*2],
	[0,1,20],[0,0,100],[0,12,100], [-10,0,10], [0, 0, 20],
);


sub OnInit {
	my $app = shift;
	my $frame = Wx::Frame->new(
		undef,-1, __PACKAGE__." $VERSION", [-1,-1],[750,465],
		&Wx::wxCAPTION | &Wx::wxCLOSE_BOX | &Wx::wxFRAME_TOOL_WINDOW | &Wx::wxSYSTEM_MENU
	);
	$frame->SetIcon( Wx::GetWxPerlIcon() );

	$app{'maltafel'} = Wx::StaticBitmap->new($frame, -1, &Wx::wxNullBitmap);
	$app{'bmp'}   = Wx::Bitmap->new( $maltafelgroesse, $maltafelgroesse);
	$app{'dc'}  = Wx::MemoryDC->new();
	$app{'dc'}->SelectObject( $app{'bmp'} );
	$app{'panel'} = create_knob_panel($frame);

	my $sizer = Wx::BoxSizer->new( &Wx::wxHORIZONTAL);
	$sizer->Add( $app{'maltafel'}, 0, &Wx::wxALL|&Wx::wxGROW, 0);
	$sizer->Add( $app{'panel'},    1, &Wx::wxALL|&Wx::wxGROW, 0);
	$frame->SetSizer( $sizer );

	set_defaults('all');
	repaint();
	$frame->Center();
	$frame->Show(1);
	$app->SetTopWindow($frame);
	1;
}

sub create_knob_panel {
	my $frame = shift;
	my $app = $frame->GetParent;
	my %sizer;
	my $panel = Wx::Panel->new($frame, -1);                       # right side control panel
	$sizer{'main'} = Wx::BoxSizer->new(&Wx::wxVERTICAL);

	my $std_al = &Wx::wxALIGN_CENTER_VERTICAL | &Wx::wxALL;
	my $sizer_al = &Wx::wxALL|&Wx::wxGROW;
	my $last_al = &Wx::wxLEFT|&Wx::wxRIGHT|&Wx::wxGROW;
	for my $nr (0 .. $#ranges) {
		my $name = $name[$nr];
		my $frontlabel = Wx::StaticText->new($panel, -1, $label[$nr].' : ');
		my $slider = $panel->{'slider_'.$name} = Wx::Slider->new($panel,-1,0,0,1);
		my $text = $panel->{'text_'.$name} = 
			Wx::TextCtrl->new($panel,-1,'',[-1,-1],[40,-1], &Wx::wxTE_READONLY);

		$sizer{$name} = Wx::BoxSizer->new(&Wx::wxHORIZONTAL);
		$sizer{$name}->Add($frontlabel, 0, $std_al, 5 );
		$sizer{$name}->Add($slider,     1, $std_al, 5 );
		$sizer{$name}->Add($text,       0, $std_al, 0 );
		Wx::Event::EVT_SCROLL( $slider, sub {
			$text->SetValue( $slider->GetValue) if $text->GetValue ne $slider->GetValue;
		} );
		Wx::Event::EVT_SCROLL_THUMBRELEASE( $slider, sub { repaint() } );
		Wx::Event::EVT_MIDDLE_DOWN( $slider, sub { set_defaults($nr); repaint() } );
	}
	$panel->{'invers'} = Wx::CheckBox->new($panel, -1,'Y - Richtung invers');
	Wx::Event::EVT_CHECKBOX( $panel->{'invers'}, -1, sub { repaint() } );

	$sizer{'rotline'} = Wx::BoxSizer->new(&Wx::wxHORIZONTAL);
	for my $label (qw(keine links rechts)) {
		my $name = $label.'rot';
		$panel->{$name} = Wx::RadioButton->new($panel, -1, $label);
		$sizer{'rotline'}->Add( $panel->{$name}, 0, $sizer_al, 5);
		Wx::Event::EVT_RADIOBUTTON($panel->{$name}, -1, sub { repaint() } )
	}
	for my $label (qw(Frequenz Startamplitude Rotation)){
		my $bname = lc substr($label, 0, 4) . '_box';
		$sizer{$bname} = Wx::StaticBoxSizer->new
			( Wx::StaticBox->new($panel, -1, " $label "), &Wx::wxVERTICAL);
		$sizer{'main'}->Add($sizer{$bname}, 0, $sizer_al, 5);
	}
	$sizer{'freq_box'}->Add($sizer{ $_ },       0, $sizer_al, 5) for qw(freqx freqy);
	$sizer{'freq_box'}->Add($panel->{'invers'}, 0, &Wx::wxLEFT, 45 );
	$sizer{'freq_box'}->AddSpacer(5);
	$sizer{'star_box'}->Add($sizer{ $_ },       0, $sizer_al, 5) for qw(ampx ampy);
	$sizer{'rota_box'}->Add($sizer{'rotline'},  0, &Wx::wxLEFT, 10);
	$sizer{'rota_box'}->Add($sizer{'rotation'}, 0, $sizer_al, 5);
	$sizer{'main'}->Add($sizer{'friction'},     0, $last_al, 10);
	$sizer{'main'}->Add($sizer{'length'  },     0, $last_al, 10);
	$sizer{'main'}->Add($sizer{'zoom'  },       0, $last_al, 10);
	$sizer{'main'}->Add($sizer{'color' },       0, $last_al|&Wx::wxBOTTOM, 10);
	$panel->SetSizer( $sizer{'main'} );
	return $panel;
}

sub set_defaults {
	my $which = shift;
	if ($which eq 'all'){
		$app{'panel'}{'keinerot'}->SetValue(1);
		set_defaults($_) for 0 .. $#ranges;
	} else {
		my $name = '_' . $name[$which];
		$app{'panel'}{'slider'.$name}->SetRange(@{$ranges[$which]}[0,2]);
		$app{'panel'}{'slider'.$name}->SetValue($ranges[$which][1]);
		$app{'panel'}{'text' . $name}->SetValue($ranges[$which][1]);
	}
}

sub repaint {
	$app{'dc'}->Clear();
	my $panel = $app{'panel'};
	my $pi = 3.141592654; # deg2rad($degrees);
	my $dx = .01 / $panel->{'slider_freqx'}->GetValue();
	my $dy = .01 / $panel->{'slider_freqy'}->GetValue();
	$dy = $panel->{'invers'}->IsChecked() ? - $dy : $dy;
	my $x = asin( ($panel->{'slider_ampx'}->GetValue-$amp)/$amp );
	my $y = asin( ($panel->{'slider_ampy'}->GetValue-$amp)/$amp );
	my $drot = $panel->{'slider_rotation'}->GetValue / 2000;
	$drot = - $drot if $panel->{'rechtsrot'}->GetValue();
	my $friction = 1 - 0.000001 * $panel->{'slider_friction'}->GetValue;
	my $duration = 4000 * $panel->{'slider_length'}->GetValue;
	my $zoomfaktor = 1.2**$panel->{'slider_zoom'}->GetValue;
	my $colormorph = $panel->{'slider_color'}->GetValue/1000;
	my $color = 0;
	my $rot = 0;
	my $cur_amplit = $amp;
	for my $cc (0 .. $duration) {
		$color += $colormorph;
		$color = 0 if $color > 1536;
		my ($r, $g, $b) = (0,0,0);
		if    ($color <  256){ $r = 255; $g = $color;}
		elsif ($color <  512){ $g = 255; $r = 511  - $color;}
		elsif ($color <  768){ $g = 255; $b = $color-511;}
		elsif ($color < 1024){ $b = 255; $g = 1023 - $color;}
		elsif ($color < 1280){ $b = 255; $r = $color - 1023;}
		elsif ($color < 1535){ $r = 255; $b = 1534 - $color;}
		$app{'dc'}->SetPen( Wx::Pen->new( Wx::Colour->new( $r, $g, $b ), 1, &Wx::wxSOLID) );
		my $xs = sin($x += $dx);
		my $ys = sin($y += $dy);
		$cur_amplit *= $friction;
		$rot += $drot;
		($xs, $ys) = rotate($xs, $ys, $rot) unless $panel->{'keinerot'}->GetValue();
		$app{'dc'}->DrawPoint( ($xs*$cur_amplit*$zoomfaktor)+$midoffset,
		                       ($ys*$cur_amplit*$zoomfaktor)+$midoffset );
	}
	$app{'maltafel'}->SetBitmap( $app{'bmp'} );
	$app{'dc'}->SelectObject( $app{'bmp'} );
	$app{'maltafel'}->Refresh();
	$panel->{'text_'.$name[$_]}->SetValue( $panel->{'slider_'.$name[$_]}->GetValue) for 0 .. $#ranges;
}

sub rotate {
	my ($x, $y, $r) = @_;
	my ($sinr, $cosr) = (sin($r), cos($r));
	return ($cosr*$x - $sinr*$y, $sinr*$x + $cosr*$y);
}

package main;
Harmonograph->new->MainLoop;