25.11.2014 Views

Fun With Perl. Blips and Beeps. DC Perl ... - The Lack Thereof

Fun With Perl. Blips and Beeps. DC Perl ... - The Lack Thereof

Fun With Perl. Blips and Beeps. DC Perl ... - The Lack Thereof

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

NoiseGen<br />

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

<strong>DC</strong> <strong>Perl</strong> Mongers, March 2012<br />

Brock Wilcox<br />

awwaiid@thelackthereof.org


Let's build a software synthesizer<br />

in about 20 minutes.


What is sound?


Let's reverse-engineer!


Exercises for reader:<br />

Speakers → Ears → Consciousness


Let's start with speakers


I like it better sideways...


Alternating Current


Sound = Speaker movement = Electrical input


Waves


What is digital sound?


What is digital sound?


Divide into samples


A sample is number from -1 .. 1


(y-axis)


Evenly spaced over time


(x-axis)


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


What does it matter?


Amplitude <strong>and</strong> Frequency


Amplitude is easy!


amplitude = volume


Frequency not so easy


frequency = wiggle speed = pitch


Low bit depth<br />

causes volume-aliasing


Low sample rate<br />

causes frequency-aliasing


So!


We just gotta generate samples!


48000 of them per second!


No problem.


Audio::PortAudio


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


# smallbeep.pl<br />

my $stream =<br />

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

->default_output_device<br />

->open_write_stream(<br />

{ channel_count => 1 },<br />

48000,<br />

100<br />

);


# 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 );


# smallnoise.pl<br />

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


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.


Not very configurable or dynamic though.


# What I want:<br />

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

# ...<br />

my $sample = $sample_gen->();


This is called a 'generator'


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 />

}


# Audio::NoiseGen<br />

# Turn constants into generators<br />

sub generalize {<br />

my %params = @_;<br />

foreach my $name (keys %params) {<br />

unless(ref $params{$name} eq 'CODE') {<br />

my $val = $params{$name};<br />

$params{$name} = sub { $val };<br />

}<br />

}<br />

return %params;<br />

}


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

into Audio::NoiseGen


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

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


Why would we use generators<br />

as parameters?


Now we're cooking with FIRE!


What's with this '440' stuff?


That's an 'A' note.


I got a whole list of them from the internets.


Stuck 'em in a hash.<br />

$note_freq{'A4'} == 440


Why 440, you say?


Remember,<br />

ear → consciousness<br />

is your problem :)


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

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


Now plays forever


Scale the volume


Amplify Generator


Envelope Generator


Attack, [Decay], Sustain, Release


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 />

])


Note / Song Generators


note( note => 'A4' )


segment( notes => 'A4 F3' ),


combine( gens => [<br />

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

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

])


Input Control


Let's build something we can PLAY


<strong>With</strong> my 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 />

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

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

Saved successfully!

Ooh no, something went wrong!