Source

App::Harmonograph / bin / harmonograph.pl

Full commit
#!/usr/bin/perl
use v5.12;
use warnings;
use Wx;
use Wx::Perl::RadioGroup;
use Wx::Perl::TextSlider;
use YAML::Tiny;
use Math::Trig;


package Harmonograph;
our $VERSION = '0.78';
use base qw(Wx::App);
my $maltafelgroesse = 500;
my $midoffset = $maltafelgroesse / 2;
my $amp = 220;
my %range_defaults = ( # label, min, max, init
    freqx => ['X',      1, 1,    27], freqy    => ['Y',      1, 1,     27],
    ampx  => ['X',   $amp, 0,$amp*2], ampy     => ['Y',   $amp, 0, $amp*2],
    rota  => ['Betrag', 1, 0,    20], friction => ['Reibung',0, 0,    100],
 'length' => ['Dauer', 12, 0,   100],
     zoom => ['Zoom',   0,-10,   10], color    => ['Farbe',  0, 0,     30],
);


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

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

	my $panel = $app->{'panel'} = Wx::Panel->new($frame);                       # right side control panel
	my %sizer;
	$sizer{'main'} = Wx::BoxSizer->new(&Wx::wxVERTICAL);
	my $callback = sub { $app->repaint() };
	my $sizer_al = &Wx::wxALL|&Wx::wxGROW;
	$panel->{$_} = Wx::Perl::TextSlider->new
		($panel, @{$range_defaults{$_}}, $callback) for keys %range_defaults;

	$panel->{'yinvers'} = Wx::CheckBox->new($panel, -1,'Y - Richtung invers');
	Wx::Event::EVT_CHECKBOX( $panel->{'yinvers'}, -1, $callback );

	$panel->{'rot_richtung'} = Wx::Perl::RadioGroup->new(
		$panel, 'keine', [qw(keine links rechts)], &Wx::wxHORIZONTAL, $callback
	);

	 Wx::Perl::Box->new();
	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($panel->{ $_ },       0, $sizer_al, 5) for qw(freqx freqy);
	$sizer{'freq_box'}->Add($panel->{'yinvers'}, 0, &Wx::wxLEFT, 45 );
	$sizer{'freq_box'}->AddSpacer(5);
	$sizer{'star_box'}->Add($panel->{ $_ },       0, $sizer_al, 5) for qw(ampx ampy);
	$sizer{'rota_box'}->Add($panel->{'rot_richtung'},  0, &Wx::wxLEFT, 10);
	$sizer{'rota_box'}->Add($panel->{'rota'}, 0, $sizer_al, 5);
	$sizer{'main'}->Add( $panel->{'friction'},     0, $sizer_al, 5);
	$sizer{'main'}->Add( $panel->{'length'  },     0, $sizer_al, 5);
	$sizer{'main'}->Add( $panel->{'zoom'  },       0, $sizer_al, 5);
	$sizer{'main'}->Add( $panel->{'color' },       0, $sizer_al, 5);
	$sizer{'main'}->AddSpacer(10);
	$panel->SetSizerAndFit( $sizer{'main'} );

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

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

sub set_defaults {
	my $app = shift;
	my $which = shift;
	if ($which eq 'all'){
		$app->{'panel'}{'rot_richtung'}->ResetValue();
		$app->set_defaults($_) for keys %range_defaults;
	} 
	else { $app->{'panel'}{$which}->ResetValue }
	$app;
}

sub repaint {
	my $app = shift;
	
	$app->{'dc'}->Clear();
	my $panel = $app->{'panel'};
	my $pi = 3.141592654; # deg2rad($degrees);
	my $dx = .01 / $panel->{'freqx'}->GetValue();
	my $dy = .01 / $panel->{'freqy'}->GetValue();
	$dy = $panel->{'yinvers'}->IsChecked() ? - $dy : $dy;
	my $x = Math::Trig::asin( ($panel->{'ampx'}->GetValue - $amp)/$amp );
	my $y = Math::Trig::asin( ($panel->{'ampy'}->GetValue - $amp)/$amp );
	my $drot = $panel->{'rota'}->GetValue / 2000;
	my $rot_richtung = $panel->{'rot_richtung'}->GetValue();
	$drot = - $drot if $rot_richtung eq 'rechts';
	my $friction = 1 - 0.000001 * $panel->{'friction'}->GetValue;
	my $duration = 4000 * $panel->{'length'}->GetValue;
	my $zoomfaktor = 1.2**$panel->{'zoom'}->GetValue;
	my $colormorph = $panel->{'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 $rot_richtung eq 'keine';
		$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();
}

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

package Wx::Perl::Box;
use base qw(Wx::Panel);
sub new {
	my ($class, $parent, $label, $widgets) = @_;
	return unless ref $widgets eq 'ARRAY';
	my ($self) = $class->SUPER::new( $parent );
	my ($sizer) = Wx::StaticBoxSizer->new 
			(Wx::StaticBox->new($self, -1, " $label "), &Wx::wxVERTICAL);

	for my $widget (@$widgets) {
		next unless ref $widget and $widget->isa('Wx::Control');
		$sizer->Add( $widget, 0, &Wx::wxLEFT, 0); # 10
		$widget->Reparent( $self );
	}
	$self->SetSizerAndFit( $sizer );
	$self;
}


package main;
Harmonograph->new->MainLoop;