14.11.2014 Views

Fun With Perl. Blips and Beeps. YAPC::NA 2012 ... - The Lack Thereof

Fun With Perl. Blips and Beeps. YAPC::NA 2012 ... - The Lack Thereof

Fun With Perl. Blips and Beeps. YAPC::NA 2012 ... - The Lack Thereof

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Audio::NoiseGen<br />

<strong>Fun</strong> <strong>With</strong> <strong>Perl</strong>. <strong>Blips</strong> <strong>and</strong> <strong>Beeps</strong>.<br />

<strong>YAPC</strong>::<strong>NA</strong> <strong>2012</strong><br />

Brock Wilcox<br />

awwaiid@thelackthereof.org


Let's build a software synthesizer<br />

in about 20 minutes.


Let's start with speakers


I like it better sideways...


"Shape" affects sound


sine wave shape


complex song shape


Divide into samples


A sample is number from -1 .. 1


(y-axis)


Evenly spaced over time


(x-axis)


digital sound


digital sound


Samples per second<br />

↳ "Sample Rate"


Common sample rates:<br />

DVD: 48,000<br />

CD: 44,100<br />

VOIP: 16,000<br />

Telephone: 8,000


Representation of each sample<br />

↳ "Bit Depth"


8 bit: 0..255 (old video games)<br />

16 bit: 0..65535 (CD)<br />

24 bit: 0..16777215 (DVD-Audio)


Human Ear: 21 bit @ 40k


So!


We just gotta generate <strong>and</strong> play samples!


48000 of them per second!


No problem.


Audio::PortAudio


Alternatives: /dev/dsp, aplay, ...


# smallnoise.pl<br />

my $stream =<br />

Audio::PortAudio::default_host_api()<br />

->default_output_device<br />

->open_write_stream(<br />

{ channel_count => 1 }, # Mono<br />

48000, # sample rate<br />

100 # sample buffer<br />

);


# smallnoise.pl<br />

while(1) {<br />

my $sample = r<strong>and</strong>(2) - 1; # from -1 .. 1<br />

my $raw = pack "f*", $sample;<br />

$stream->write($raw);<br />

}


# smallnoise.pl<br />

my $sample = r<strong>and</strong>(2) - 1;


# smallbeep.pl<br />

my $time = 0;<br />

my $time_step = 1 / 48000;<br />

while(1) {<br />

my $raw = '';<br />

for(1..100) { # Build up 100 samples<br />

my $sample = sin( 2 * 3.1415 * $time * 440 );<br />

$time += $time_step;<br />

$raw .= pack "f*", $sample;<br />

}<br />

$stream->write($raw);<br />

exit if $time > 1; # don't beep forever!<br />

}


Woo... beeping!


# smallbeep.pl<br />

my $sample = sin( 2 * 3.1415 * $time * 440 );


Two beeps at once?


Turns out you just add waves together


# smallbeep2.pl<br />

my $sample = sin( 2 * 3.1415 * $time * 440 );<br />

$sample += sin( 2 * 3.1415 * $time * 349.23 );<br />

$sample /= 2; # Avoid clipping!


Let's generalize


my $sample = get_next_sample();


Call get_next_sample() over <strong>and</strong> over


Get a new sample each time


sub sine {<br />

return sin( 2 * 3.1415 * $time * 440 );<br />

}


That was easy.


This is called a 'generator'


Not very configurable or dynamic though.


# What I want:<br />

my $sample_gen = sine(440);<br />

# ...<br />

my $sample = $sample_gen->();


We can make this using a closure!


(aka subref with bound variables)


sub sine {<br />

my $freq = shift;<br />

return sub {<br />

return sin( 2 * 3.1415 * $time * $freq );<br />

}<br />

}


# So now we have it:<br />

my $sample_gen = sine(440);<br />

# ... in 'play'<br />

my $sample = $sample_gen->();


# Create a 440 Hz sine generator<br />

my $gen = sine(440);<br />

# Play it!<br />

play( $gen );


play( sine( 440 ) );


One more generator tweak


Parameterize generators with generators,<br />

<strong>and</strong> use named params


# Audio::NoiseGen<br />

sub sine {<br />

my %params = generalize( freq => 440, @_ );<br />

return sub {<br />

return sin( 2 * 3.1415 * $time * $params{freq}->() );<br />

}<br />

}


my $lfo = sine( freq => 5 );<br />

my $vfreq = sub {<br />

}<br />

$lfo->() * 100 + 440<br />

play( gen =><br />

)<br />

sine( freq => $vfreq )


I've put these <strong>and</strong> others<br />

into Audio::NoiseGen


use Audio::NoiseGen qw(:all);<br />

init();<br />

play( gen => sine( freq => 440 ) );


Now we're cooking with FIRE!


Plays forever


Envelope Generator


envelope(<br />

attack => 0.5,<br />

sustain => 1,<br />

release => 0.5,<br />

gen => sine( freq => 440 )<br />

)


eturns undef when done


Sequence Generator


sequence( gens => [<br />

envelope( sustain => 1, gen => sine( freq => 440 ) ),<br />

envelope( sustain => 1, gen => sine( freq => 349.23 ) ),<br />

])


Combine Generator


combine( gens => [<br />

envelope( sustain => 1, gen => sine( freq => 440 ) ),<br />

envelope( sustain => 1, gen => sine( freq => 349.23 ) ),<br />

])


Input Control


Let's build something we can PLAY


Touch-screen


$ xmousepos<br />

838 574 221 170


my ($x, $y) = split(' ', `xmousepos`);


# Generate frequency based on mouse-x<br />

sub mousefreq {<br />

my $c = 0;<br />

my ($x, $y) = (0, 0);<br />

return sub {<br />

# Don't update too often<br />

unless($c++ % 1000) {<br />

($x, $y) = split(' ', `xmousepos`);<br />

print "pos: $x, $yn";<br />

}<br />

return $x;<br />

}<br />

}


play( gen =><br />

amp(<br />

amount => mousevol(),<br />

gen => sine(<br />

freq => mousefreq()<br />

)<br />

)<br />

);


And now we have a synth :)


THE END


References<br />

* https://metacpan.org/module/Audio::NoiseGen<br />

* https://metacpan.org/module/Audio::PortAudio<br />

* http://thelackthereof.org/NoiseGen<br />

* https://github.com/awwaiid/perl-noise<br />

* http://people.xiph.org/~xiphmont/demo/neil-young.html


Better/Similar projects<br />

* csound<br />

* ChucK<br />

* PureData<br />

* SuperCollider<br />

* http://en.wikipedia.org/wiki/Comparison_of_audio_synthesis_environments


Audio::NoiseGen<br />

<strong>Fun</strong> <strong>With</strong> <strong>Perl</strong>. <strong>Blips</strong> <strong>and</strong> <strong>Beeps</strong>.<br />

<strong>YAPC</strong>::<strong>NA</strong> <strong>2012</strong><br />

Brock Wilcox<br />

awwaiid@thelackthereof.org


BONUS MATERIAL


Note / Song Generators


note( note => 'A4' )


segment( notes => 'A4 F3' ),


combine( gens => [<br />

segment( notes => 'A4' ),<br />

segment( notes => 'F3' ),<br />

])

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!