package Animation::Animation;

use feature "say";

use Image::Magick;
use Data::Dumper;
use Math::Trig;
use Tie::Cfg;
use File::Spec::Functions;

sub new
{
    my $class = shift;
    my $self = bless {arguments => shift}, $class;
    $self->setConfig();
    $self->setFrames();
    $self;
}

sub setConfig
{
    my $self = shift;
    my $path = $FindBin::Bin . "/config";
    tie my %global_config, "Tie::Cfg", READ=>$path, SEP=>"=";
    $self->{"global_config"} = \%global_config;
    if ($self->getOption("config"))
    {
	tie my %local_config, "Tie::Cfg", READ=>$self->getOption("config"), SEP=>"=";
	$self->{"local_config"} = \%local_config;
    }
}

sub setFrames
{
    my ($self, $frames) = @_;
    $self->{"frames"} = $frames;
}

sub getOption
{
    my ($self, $name, $section) = @_;
    my $arguments = $self->{"arguments"};
    my $local_config = $self->{"local_config"};
    my $global_config = $self->{"global_config"};
    my $value;
    if ($section)
    {
	$compound_name = "$section-$name";
	if (exists $arguments->{$compound_name})
	{
	    $value = $arguments->{$compound_name};
	}
	elsif ($local_config && exists $local_config->{$section}{$name})
	{
	    $value = $local_config->{$section}{$name};
	}
	elsif (exists $global_config->{$section}{$name})
	{
	    $value = $global_config->{$section}{$name};
	}
    }
    else
    {
	if (exists $arguments->{$name})
	{
	    $value = $arguments->{$name};
	}
	elsif ($local_config && exists $local_config->{$name})
	{
	    $value = $local_config->{$name};
	}
	elsif (exists $global_config->{$name})
	{
	    $value = $global_config->{$name};
	}
    }
    $value;
}

sub buildPoints
{
    my ($self, $shape, $radius, $cx, $cy, $rotation, $cross_angle, $cross_minor_radius_size) = @_;
    my @points;
    for ($shape)
    {
	if (/^star$/)
	{
	    @points = $self->buildStarPoints($radius, $cx, $cy, $rotation);
	}
	elsif (/^triangle$/)
	{
	    @points = $self->buildPolygonPoints(3, $radius, $cx, $cy, $rotation);
	}
	elsif (/^circle$/)
	{
	    @points = $self->buildCirclePoints($radius, $cx, $cy);
	}
	elsif (/^square$/)
	{
	    @points = $self->buildPolygonPoints(4, $radius, $cx, $cy, $rotation);
	}
	elsif (/^cross$/)
	{
	    @points = $self->buildCrossPoints($radius, $cross_angle, $cx, $cy, $rotation,
					      $cross_minor_radius_size);
	}
    }
    @points;
}

sub buildStarPoints
{
    my ($self, $radius, $cx, $cy, $rotation) = @_;
    my $count = 10;
    my $step = deg2rad(360 / $count);
    my @points;
    my ($len, $angle, $x, $y);
    for (0..$count - 1)
    {
	$len = $radius;
	if ($_ % 2)
	{
	    $len /= 2;
	}
	$angle = $rotation + $step * $_;
	push @points, [$self->getPointOnCircle($len, $angle, $cx, $cy)];
    }
    @points;
}

sub buildPolygonPoints
{
    my ($self, $sides, $radius, $cx, $cy, $rotation) = @_;
    my $step = deg2rad(360 / $sides);
    my @points;
    for (0..$sides - 1)
    {
	$angle = $rotation + $step * $_;
	push @points, [$self->getPointOnCircle($radius, $angle, $cx, $cy)];
    }
    @points;
}

sub buildCirclePoints
{
    my ($self, $radius, $cx, $cy) = @_;
    [$cx, $cy], [$cx, $cy + $radius];
}

sub buildCrossPoints
{
    my ($self, $radius, $angle, $cx, $cy, $rotation, $minor_radius_size) = @_;
    my @points;
    my $step = deg2rad(90);
    my ($x, $y, $offset, $minor_radius);
    for (0..3)
    {
	$offset = $step * $_;
	push @points, [$self->getPointOnCircle($radius, $rotation + $offset, $cx, $cy)];
	push @points, [$self->getPointOnCircle($radius, $rotation + $offset + $angle, $cx, $cy)];
	$minor_radius = sqrt((2 * (sin($angle * $minor_radius_size) * $radius)) ** 2);
	push @points, [$self->getPointOnCircle($minor_radius,
					       $rotation + $offset + $angle + ($step - $angle) / 2,
					       $cx, $cy)];
    }
    @points;
}

sub getPointOnCircle
{
    my ($self, $radius, $angle, $cx, $cy) = @_;
    my $x = $self->round(($radius * sin($angle)) + $cx);
    my $y = $self->round(($radius * cos($angle)) + $cy);
    $x, $y;
}

sub round
{
    int(@_[1] + .5);
}

sub getPrimitive
{
    @_[1] eq "circle" ? @_[1] : "polygon";
}

sub buildPointsString
{
    my $points = @_[1];
    my $str = "";
    my @point;
    for (0..$#{$points})
    {
	@point = @{${$points}[$_]};
	$str .= $point[0] . "," . $point[1] . " ";
    }
    $str;
}

sub getColor
{
    my ($self, $gradient, $index, $resolution) = @_;
    int(($index % $resolution + 1) * ($#{$gradient} + 1) / $resolution) - 1;
}

sub wrapIndex
{
    @_[1] % (@_[2] + 1);
}

sub readGradient
{
    my ($self, $name) = @_;
    my $gradient = Image::Magick->new;
    $gradient->read($self->findGradientFile($name));
    my @colors;
    for (0..$gradient->Get("width") - 1)
    {
	push @colors, $self->buildColorName($gradient->GetPixel(x=>$_, 'y'=>0, map=>'str',
								normalize=>"False"));
    }
    @colors;
}

sub findGradientFile
{
    my ($self, $name) = @_;
    my $section = "gradients";
    my $root = $FindBin::Bin . "/" . $self->getOption("root", "gradients");
    say "root: " . $root;
    for (glob $root . "*")
    {
	if (/$name\..*$/)
	{
	    return $_;
	}
    }
}

sub buildColorName
{
    "rgb(" . (@_[1] >> 8) . ", " . (@_[2] >> 8) . ", " . (@_[3] >> 8) . ")";
}

sub write
{
    my $self = shift;
    my $destination = $self->getDestination();
    my $frames = $self->{"frames"};
    if (!$self->getOption("split"))
    {
	$frames->Write($destination);
    }
    else
    {
	mkdir $destination;
	my $delay;
	for ($ii = 0; $frames->[$ii]; $ii++)
	{
	    $delay = $frames->[$ii]->Get("delay");
	    $frames->[$ii]->Write("$destination/$ii-$delay.png");
	}
    }
    say "wrote: $destination";
}

sub getDestination
{
    my $self = shift;
    my $root = $self->getOption("destination", "file");
    my $pad_length = $self->getOption("name-pad-length", "file");
    my $destination;
    if ($self->getOption("out"))
    {
	$destination = $self->getOption("out");
    }
    elsif (!$self->getOption("split"))
    {
	$destination = $self->buildPathPrefix($self->findSlot("$root/*.gif"));
	$destination .= ".gif";
    }
    else
    {
	$destination = $self->buildPathPrefix($self->findSlot("$root/*/"));
    }
    $destination;
}

sub findSlot
{
    my $self = shift;
    my @existing = glob shift;
    for (1..@existing)
    {
	$existing[$_ - 1] =~ /([0-9]+)/;
	if ($_ < int($1))
	{
	    return $_;
	}
    }
    @existing + 1;
}

sub buildPathPrefix
{
    my $self = shift;
    my $pad_length = $self->getOption("name_pad_length", "file");
    $self->getOption("destination", "file") . sprintf("%0${pad_length}d", shift);
}

1;
216.73.216.211
216.73.216.211
216.73.216.211
 
September 13, 2013

from array import array
from time import sleep

import pygame
from pygame.mixer import Sound, get_init, pre_init

class Note(Sound):

    def __init__(self, frequency, volume=.1):
        self.frequency = frequency
        Sound.__init__(self, self.build_samples())
        self.set_volume(volume)

    def build_samples(self):
        period = int(round(get_init()[0] / self.frequency))
        samples = array("h", [0] * period)
        amplitude = 2 ** (abs(get_init()[1]) - 1) - 1
        for time in xrange(period):
            if time < period / 2:
                samples[time] = amplitude
            else:
                samples[time] = -amplitude
        return samples

if __name__ == "__main__":
    pre_init(44100, -16, 1, 1024)
    pygame.init()
    Note(440).play(-1)
    sleep(5)

This program generates and plays a 440 Hz tone for 5 seconds. It can be extended to generate the spectrum of notes with a frequency table or the frequency formula. Because the rewards in Send are idealized ocean waves, they can also be represented as tones. Each level has a tone in its goal and a tone based on where the player's disc lands. Both play at the end of a level, sounding harmonic for a close shot and discordant for a near miss. The game can dynamically create these tones using the program as a basis.

I'm also building an algorithmically generated song: Silk Routes (Scissored). Here is an example of how it sounds so far.