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 )