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