Arduino用の正弦波を検討してみる

+256から-256の範囲で正弦波を出力したいんだけど、
理想的には配列も添字も8bit変数に納めたくて、いろいろやってみた。

use v5.14;
use strict;
use warnings;

use Imager;
use Math::Trig qw( pi );
use constant PI => pi();

my $img = Imager->new(
    xsize => 1000, ysize => 600 + 1 );

$img->box( filled => 1, color => 'white' );
draw_wave( $img, 'green' , create_sin_osc() , .00, .0015 );
draw_wave( $img, 'blue'  , create_sin_osc1(), .10, .0015 );
draw_wave( $img, 'purple', create_sin_osc2(), .20, .0015 );
draw_wave( $img, 'red'   , create_sin_osc3(), .30, .0015 );
$img->write( file => "graph.png" ) or die $img->errstr;

sub draw_wave {
    my ( $img, $color, $osc, $t0, $dt ) = @_;
    my $y0 = int($img->getheight() / 2);
    my $ymax = $y0;

    my $xmax = $img->getwidth() - 1;
    foreach my $x ( 0..$xmax ) {
        my $t = $t0 + ($x * $dt);
        $img->setpixel(
            x => $x, y => ($y0 - $osc->($t)), color => $color );
    }

#    my @points = map {
#        [ $_, ($y0 - $osc->($_ * 0.001)) ];
#    } 0..$xmax;
#    $img->polyline( points => \@points, color => $color );
}

sub create_sin_osc {
    return sub {
        my $t = shift;
        return int( 256.0 * sin($t * 2.0 * PI) );
    };
}

sub create_sin_osc1 {
    my $n = 256;

    my @wav = map {
        my $tmp = sin( ($_ / $n) * (2.0 * PI) );
        int( $tmp * 64.0 );
    } 0..($n - 1);

    return sub {
        my $t = shift;
        return $wav[ int($t * $n) % $n ] * 4;
    };
}

sub create_sin_osc2 {
    my $n = 256;
    my $mask = $n - 1;

    my @wav = map {
        my $tmp = sin( ($_ / $n) * (2.0 * PI) );
        if ( $tmp < .0 ) {
            int( ($tmp * (128.0 / 1.125)) - .5 );
        }
        else {
            int( ($tmp * (128.0 / 1.125)) + .5 );
        }
    } 0..($n - 1);

    return sub {
        my $t = shift;
        my $i = int( $t * $n );
        return int( $wav[$i & $mask] * 1.125 * 2 );
    };
}

sub create_sin_osc3 {
    my $n = 128;
    my $mask = $n - 1;

    my @wav = map {
        my $tmp = sin( ($_ / $n) * (2.0 * PI) );
        if ( $tmp < .0 ) {
            int( ($tmp * (128.0 / 1.125)) - .5 );
        }
        else {
            int( ($tmp * (128.0 / 1.125)) + .5 );
        }
    } 0..($n - 1);

    say $_ for @wav;

    return sub {
        my $t = shift;
        my $i = int( $t * $n * 4 );
        my $rest = $i & 0x03;
        $i >>= 2;

        my $val = $wav[$i & $mask];
        my $diff = $wav[($i + 1) & $mask] - $val;
        my $ret = (($val * 4) + ($diff * $rest)) * 1.125;
        return int( $ret / 2 );
    };
}

LFOに使いたくて補間までやってみたけど、
補間するなら要素数を減らして、16bitの配列を使うのが良さそう。
なので、今回は補間なしで実装しようと思う。

もう一つの理由は、
矩形波やのこぎり派でも補間されちゃうのは困るので、
音を出す用とLFOで処理を分けようと思う。

ちなみにグラフはこんな感じ。
20141012

おしまい。

Leave a Comment