Audio-NoiseGen

 view release on metacpan or  search on metacpan

lib/Audio/NoiseGen.pm  view on Meta::CPAN

  'B1'  => 61.74,   'C2'  => 65.41,   'C#2' => 69.30,
  'Db2' => 69.30,   'D2'  => 73.42,   'D#2' => 77.78,
  'Eb2' => 77.78,   'E2'  => 82.41,   'F2'  => 87.31,
  'F#2' => 92.50,   'Gb2' => 92.50,   'G2'  => 98.00,
  'G#2' => 103.83,  'Ab2' => 103.83,  'A2'  => 110.00,
  'A#2' => 116.54,  'Bb2' => 116.54,  'B2'  => 123.47,
  'C3'  => 130.81,  'C#3' => 138.59,  'Db3' => 138.59,
  'D3'  => 146.83,  'D#3' => 155.56,  'Eb3' => 155.56,
  'E3'  => 164.81,  'F3'  => 174.61,  'F#3' => 185.00,
  'Gb3' => 185.00,  'G3'  => 196.00,  'G#3' => 207.65,
  'Ab3' => 207.65,  'A3'  => 220.00,  'A#3' => 233.08,
  'Bb3' => 233.08,  'B3'  => 246.94,  'C4'  => 261.63,
  'C#4' => 277.18,  'Db4' => 277.18,  'D4'  => 293.66,
  'D#4' => 311.13,  'Eb4' => 311.13,  'E4'  => 329.63,
  'F4'  => 349.23,  'F#4' => 369.99,  'Gb4' => 369.99,
  'G4'  => 392.00,  'G#4' => 415.30,  'Ab4' => 415.30,
  'A4'  => 440.00,  'A#4' => 466.16,  'Bb4' => 466.16,
  'B4'  => 493.88,  'C5'  => 523.25,  'C#5' => 554.37,
  'Db5' => 554.37,  'D5'  => 587.33,  'D#5' => 622.25,
  'Eb5' => 622.25,  'E5'  => 659.26,  'F5'  => 698.46,
  'F#5' => 739.99,  'Gb5' => 739.99,  'G5'  => 783.99,
  'G#5' => 830.61,  'Ab5' => 830.61,  'A5'  => 880.00,
  'A#5' => 932.33,  'Bb5' => 932.33,  'B5'  => 987.77,
  'C6'  => 1046.50, 'C#6' => 1108.73, 'Db6' => 1108.73,
  'D6'  => 1174.66, 'D#6' => 1244.51, 'Eb6' => 1244.51,
  'E6'  => 1318.51, 'F6'  => 1396.91, 'F#6' => 1479.98,
  'Gb6' => 1479.98, 'G6'  => 1567.98, 'G#6' => 1661.22,
  'Ab6' => 1661.22, 'A6'  => 1760.00, 'A#6' => 1864.66,
  'Bb6' => 1864.66, 'B6'  => 1975.53, 'C7'  => 2093.00,
  'C#7' => 2217.46, 'Db7' => 2217.46, 'D7'  => 2349.32,
  'D#7' => 2489.02, 'Eb7' => 2489.02, 'E7'  => 2637.02,
  'F7'  => 2793.83, 'F#7' => 2959.96, 'Gb7' => 2959.96,
  'G7'  => 3135.96, 'G#7' => 3322.44, 'Ab7' => 3322.44,
  'A7'  => 3520.00, 'A#7' => 3729.31, 'Bb7' => 3729.31,
  'B7'  => 3951.07, 'C8'  => 4186.01, 'C#8' => 4434.92,
  'Db8' => 4434.92, 'D8'  => 4698.64, 'D#8' => 4978.03,
  'Eb8' => 4978.03,
);

our @EXPORT_OK = qw(
  $sample_rate
  $time_step
  $stream
  %note_freq
  init
  play
  G
  sine
  silence
  noise
  white_noise
  triangle
  square
  envelope
  combine
  split
  sequence
  note
  rest
  segment
  formula
  hardlimit
  amp
  oneshot
  lowpass
  highpass
  generalize
);

our %EXPORT_TAGS = (
  all => [ @EXPORT_OK ]
);

=head1 INITIALIZATION AND PLAY

=head2 init($api, $device, $sample_rate)

This sets up our L<Audio::PortAudio> interface. All parameters are optional, and without any you will get the default provided by PortAudio.

=cut

sub init {
  my $api = shift || Audio::PortAudio::default_host_api();
  my $device = shift || $api->default_output_device;
  $sample_rate = shift || 48000;
  # $sample_rate = shift || 20000;
  $time_step = (1/$sample_rate); # 2 * (1/48000) = 0.0000416666
  $stream = $device->open_write_stream(
    {
      channel_count => 1,
    },
    $sample_rate,
    8000, # some sort of buffer size?
    # 0
  );
}

# sub import {
  # my $class = shift;
  # # if(grep { /^:init$/ } @_) {
    # # Audio::NoiseGen::init();
  # # }
  # $class->SUPER::import(@_);
# }

sub log10 {
  my $n = shift;
  return log($n)/log(10);
}

sub db {
  my $sample = shift;
  return (20 * log10(abs($sample)+0.00000001));
}

=head2 play(gen => $gen, filename => $filename)

C<$filename> is optional.

Invokes the C<$gen> and sends the resulting samples to the output device (soundcard).

lib/Audio/NoiseGen.pm  view on Meta::CPAN

    }
    return $sample;
  }
}

=head2 rest( length => 3 )

Play silence for a fixed set of time, 'length', in seconds.

=cut

sub rest {
  my %params = generalize( length => 0, @_ );
  my $silence = silence();
  return envelope( sustain => $params{length}, gen => $silence );
}

=head2 segment( notes => 'A B C#' )

Generate a sequence of notes by parsing the 'notes' param. Pretty minimal for now.

=cut

sub segment {
  my %params = generalize( @_ );
  my @notes;
  my $cur_gen;
  my $last_sample;
  return sub {
    if(!@notes && ! defined $last_sample) {
      my $notes = $params{notes}->();
      $notes =~ s/^\s+//;
      $notes =~ s/\s+$//;
      push @notes, split(/\s+/, $notes);
    }
    if(! defined $last_sample && @notes) {
      my $base = 0.5;
      my $note = shift @notes;
      my ($n, $f) = split '/', $note;
      $f ||= 1;
      my $l = $base / $f;
      unless( $n =~ /\d$/ ) {
        $n .= '4';
      }
      if($n =~ /^R/) {
        $cur_gen = rest(length => $l);
      } else {
        $cur_gen = note(
          note    => $n,
          attack  => 0.01,
          sustain => $l,
          release => 0.01
        );
      }
    }
    $last_sample = $cur_gen->();
    return $last_sample || 0;
  }
}

=head2 formula( formula => sub { $_*(42&$_>>10) } )

Plays a formula. Takes 'formula', 'bits', and 'sample_rate'. 'bits' defaults to 8, 'sample_rate' defaults to 8000.

Formula uses C<< $_ >> instead of 't', but is otherwise similar to what is described at L<http://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html>.

=cut

sub formula {
  my %params = generalize(
    bits        => 8,
    sample_rate => 8000,
    @_
  );
  my $formula = $params{formula};
  my $formula_increment = $params{sample_rate}->() / $sample_rate;
  my $max = 2 ** $params{bits}->();
  my $t = 0;
  return sub {
    $t += $formula_increment;
    local $_ = int $t;
    return (((
      $formula->(int $t)
    ) % $max - ($max/2))/($max/2))
  }
}

 # Return RC low-pass filter output samples, given input samples,
 # time interval dt, and time constant RC
 # function lowpass(real[0..n] x, real dt, real RC)
   # var real[0..n] y
   # var real α = dt / (RC + dt)
   # y[0] := x[0]
   # for i from 1 to n
       # y[i] = α * x[i] + (1-α) * y[i-1]
       # OR y[i] = y[i-1] + α * (x[i] - y[i-1])
   # return y

sub lowpass {
  my %params = generalize( @_ );
  my $current_time = 0;
  my $last_out_sample = 0;
  sub {
    my $gen_sample = $params{gen}->();
    return undef if ! defined $gen_sample;
    # $current_time += $time_step;
    # my $alpha = $current_time / ($params{rc}->() + $current_time);
    my $alpha = $time_step / ($params{rc}->() + $time_step);
    my $sample = $last_out_sample + $alpha * ($gen_sample - $last_out_sample);
    $last_out_sample = $sample;
    return $sample;
  }
}


 # // Return RC high-pass filter output samples, given input samples,
 # // time interval dt, and time constant RC
 # function highpass(real[0..n] x, real dt, real RC)
   # var real[0..n] y
   # var real α := RC / (RC + dt)
   # y[0] := x[0]
   # for i from 1 to n
     # y[i] := α * y[i-1] + α * (x[i] - x[i-1])
   # return y

sub highpass {
  my %params = generalize( @_ );
  my $current_time = 0;
  my $last_sample = 0;
  my $last_out_sample = 0;
  sub {
    my $gen_sample = $params{gen}->();
    my $rc = $params{rc}->();
    $current_time += $time_step;
    my $alpha = $rc / ($rc + $current_time);
    my $sample = $alpha * $last_out_sample + $alpha * ($gen_sample - $last_sample);
    $last_out_sample = $sample;
    $last_sample = $gen_sample;
    return $sample;
  }
}


######################################
# Now let's pretend to be an object

use overload
  '+' => \&m_seq,
  '*' => \&m_combine,
  '""' => sub { },
;

sub new {
  my $class = shift;
  my $gen = shift;
  if(!ref $gen) {
    print STDERR "segement '$gen'\n";
    $gen = segment($gen);
  # } elsif(ref $gen eq 'CODE') {
    # $gen = formula($gen);
  }
  my $self = {
    gen => $gen,
  };
  bless $self, $class;
  return $self;
}

sub m_seq {
  my ($self, $other, $swap) = @_;
  my $s = sequence($self->{gen}, $other->{gen});
  return Audio::NoiseGen->new($s);
}

sub m_combine {
  my ($self, $other, $swap) = @_;
  print STDERR "combine!\n";
  my $s = combine($self->{gen}, $other->{gen});
  return Audio::NoiseGen->new($s);
}

sub G {
  my $x = shift;
  return Audio::NoiseGen->new($x);
}

sub mplay {
  my $self = shift;
  play($self->{gen});
}

# (
  # ( G('C C C') + G('E E E') )
  # * ( G('D') * G('E') * G('G') ) # chord
# )->mplay;

1;



( run in 2.405 seconds using v1.01-cache-2.11-cpan-cd2fffc590a )