Perlでミョンミョンした音を作る

今日はCassisを開発するときに使ってるスクリプトを公開します。
あとはパラメータをファイルから入力できるようにして、
サンプルスクリプトとしてリポジトリに追加する予定です。

package MySynth;
use strict;
use warnings;
use Cassis;

sub new {
    my $class = shift;
    my %args = @_;

    my $fs = ( exists $args{fs} ) ? $args{fs} : 44100;
    bless {
        samples => [],
        fs      => $fs,
        modules => {
            DCO1 => Cassis::DCO::Saw->new( fs => $fs ),
            DCO2 => Cassis::DCO::Tri->new( fs => $fs ),
            LPF  => Cassis::Iir2::LPF->new( cutoff => 0.01, q => 6.0 ),
            LFO1 => Cassis::Osc::Pulse->new( fs => $fs ),
            LFO2 => Cassis::Osc::Sin->new( fs => $fs ),
            AMP  => Cassis::Amp->new(),
            EG1  => Cassis::EG->new(
                fs => $fs,
                adsr => [ 0.03, 0.1, 0.5, 0.5 ],
                curve => 0.5
            ),
            EG2  => Cassis::EG->new(
                fs => $fs,
                adsr => [ 0.03, 0.4, 0.2, 0.5 ],
                curve => 0.5
            )
        }
    }, $class;
}

sub exec {
    my $self = shift;
    my %args = @_;

    my ( $dco1, $dco2, $lpf, $lfo1, $lfo2, $amp, $env1, $env2 ) = map {
        $self->{modules}->{$_};
    } qw(DCO1 DCO2 LPF LFO1 LFO2 AMP EG1 EG2);
    $dco1->set_pitch( $args{pitch} - 0.01) if ( exists $args{pitch} );
    $dco2->set_pitch( $args{pitch} - 1.0 ) if ( exists $args{pitch} );
    $lfo1->set_freq( 2000 );
    $lfo2->set_freq( 2200 );
    $amp->set_volume( 0.5 );
    $env1->trigger( gatetime => 1.0 );
    $env2->trigger( gatetime => 0.5 );

    my $lfo1_out = $lfo1->exec( num => $args{num} );
    my $lfo2_out = $lfo2->exec( num => $args{num} );
    my $dco1_out = $dco1->exec( num => $args{num}, mod_pitch => {
        src => $lfo1_out, depth => 0.2
    } );
    my $dco2_out = $dco2->exec( num => $args{num}, mod_pitch => {
        src => $lfo2_out, depth => 0.1
    } );
    my $filter_out = $lpf->exec( src => mix($dco1_out, $dco2_out, 0.5), mod_cutoff => {
        src => $env2->exec( num => $args{num} ), depth => 0.5
    } );
    my $amp_out = $amp->exec( src => $filter_out, mod_volume => {
        src => $env1->exec( num => $args{num} ), depth => 1.0
    } );

    push @{$self->{samples}}, @{$amp_out};
}

sub write {
    my $self = shift;
    my %args = @_;

    $args{sf}       = $self->{sf};
    $args{channels} = [ $self->{samples} ];

    Cassis::File::write( %args );
}

sub mix {
    my ( $src1, $src2, $gain ) = @_;
    my $n = scalar( @{$src1} );
    my @dst = map { ($src1->[$_] + $src2->[$_]) * $gain; } 0..($n - 1);
    return \@dst;
}

package main;
use strict;
use warnings;

my %NOTE_TO_PITCH = (
    C => -9 / 12,
    D => -7 / 12,
    E => -5 / 12,
    F => -4 / 12,
    G => -2 / 12,
    A =>  0 / 12,
    B =>  2 / 12
);

my $s = MySynth->new();
foreach ( qw(C4 D4 E4 F4 G4 A4 B4 C5) ) {
    if ( my ( $note, $octave ) = m/([\w])(\d)/i ) {
        print 'pitch: ', $octave + $NOTE_TO_PITCH{$note}, "\n";
        $s->exec(
            pitch => $octave + $NOTE_TO_PITCH{$note},
            num => 44100 * 0.5
        );
    }
    else {
        warn 'cannot parse => ', $_;
    }
}

$s->write( file => 'sample.wav' );

まだ、音作りのコツがつかめてないのですが、
実際は、ランダムパラメータでWAVとパラメータと出力して、
気に入った音が見つかったら詰めていこうと思っています。

個人的に、非常に満足しております。

おしまい。

Leave a Comment