Audio::PortAudio(実践編1)

前回との差分は、エンベロープが追加された辺りなんだけど、
音程を変えるタイミングでtriggerを呼ぶと鍵盤を押した感じになる。

use strict;
use warnings;
use Cassis;
use Audio::PortAudio;

use constant SAMPLING_RATE => 44100;
use constant INT16_MAX => 0x7FFF;

my @pitch_table = (
    -9 / 12, # C
    -7 / 12, # D
    -5 / 12, # E
    -4 / 12, # F
    -2 / 12, # G
     0 / 12, # A
     2 / 12, # B
    (-9 / 12) + 1.0 # C(ここから1オクターブ上がる)
);

play();

sub play {
    my ( $frames_per_buffer, $stream_flags ) = ( 512, undef );
    my $api = Audio::PortAudio::default_host_api();
    printf STDERR "Going to play via %s\nCtrl+c to stop...", $api->name;
    my $device = $api->default_output_device;
    my $stream = $device->open_write_stream( {
            channel_count => 1, # 1:mono, 2:stereo
            sample_format => 'int16' #  'float32', 'int16', 'int32', 'int24', 'int8', 'uint8'
        },
        SAMPLING_RATE,
        $frames_per_buffer,
        $stream_flags,
    );

    my $dco = Cassis::DCO::Tri->new( fs => SAMPLING_RATE );
    my $eg = Cassis::EG->new(
        fs      => SAMPLING_RATE,
        adsr    => [ 0.02, 0.02, 0.5, 1.0 ],
        curve   => 0.7
    );
    my $amp = Cassis::Amp->new();
    my $interval = SAMPLING_RATE;
    my $i = $interval;

    # Infinite loop...
    while ( 1 ) {
        my $wa = $stream->write_available;

        if ( $interval <= $i ) {
            $eg->trigger( gatetime => 0.3 );
            my $pitch = shift @pitch_table;
            $dco->set_pitch( 4.0 + $pitch );
            push @pitch_table, $pitch;
            $i -= $interval;
        }

        my $amp_out = $amp->exec(
            src => $dco->exec( num => $wa ),
            mod_volume => {
                src => $eg->exec( num => $wa ),
                depth => 1.0
            }
        );

        my $buf = pack( 's*', map {
            int( (($_ < -1.0) ? -1 : ((1.0 < $_) ? +1 : $_)) * INT16_MAX );
        } @{$amp_out} );

        $stream->write( $buf );
        $i += $wa;
    }
}

最初は、note_onnote_offっていうサブルーチンを用意してたんだけど、
note_offを呼ぶの面倒すぎると判断したので、
triggerの引数にgatetimeを指定するような実装にした。

あと1回くらい、似たようなコードを披露することになると思うけど、
たぶん、予想通りかと思います。

おしまい。

Leave a Comment