NoiseGen at Arlington Ruby 2012

96
noise.rb How to annoy your cat with sound generators Arlington Ruby, May 2012 Brock Wilcox [email protected]

description

Brief introduction to the mechanics of digital sound, generator functions, and mixing the two to make a software synthesizer in Ruby.

Transcript of NoiseGen at Arlington Ruby 2012

Page 1: NoiseGen at Arlington Ruby 2012

noise.rbHow to annoy your cat with sound generators

Arlington Ruby, May 2012Brock [email protected]

Page 2: NoiseGen at Arlington Ruby 2012

Let's build a software synthesizerin about 20 40 minutes.

Page 3: NoiseGen at Arlington Ruby 2012

Digital Sound

Page 4: NoiseGen at Arlington Ruby 2012

Exercises for reader:

Speakers → Ears → Consciousness

Page 5: NoiseGen at Arlington Ruby 2012

Let's start with speakers

Page 6: NoiseGen at Arlington Ruby 2012
Page 7: NoiseGen at Arlington Ruby 2012

Sideways is even better

Page 8: NoiseGen at Arlington Ruby 2012
Page 9: NoiseGen at Arlington Ruby 2012
Page 10: NoiseGen at Arlington Ruby 2012

Alternating Current

Page 11: NoiseGen at Arlington Ruby 2012

Sound = Speaker movement = Electrical input

Page 12: NoiseGen at Arlington Ruby 2012

Waves

Page 13: NoiseGen at Arlington Ruby 2012

digital?

Page 14: NoiseGen at Arlington Ruby 2012
Page 15: NoiseGen at Arlington Ruby 2012

Divide into samples

Page 16: NoiseGen at Arlington Ruby 2012

A sample is number from -1 .. 1

Page 17: NoiseGen at Arlington Ruby 2012

(y-axis)

Page 18: NoiseGen at Arlington Ruby 2012

Evenly spaced over time

Page 19: NoiseGen at Arlington Ruby 2012

(x-axis)

Page 20: NoiseGen at Arlington Ruby 2012

Samples per second

↳ "Sample Rate"

Page 21: NoiseGen at Arlington Ruby 2012

Common sample rates:

DVD: 48,000CD: 44,100VOIP: 16,000Telephone: 8,000

Page 22: NoiseGen at Arlington Ruby 2012

Representation of each sample

↳ "Bit Depth"

Page 23: NoiseGen at Arlington Ruby 2012

8 bit: 0..255 (old video games)

16 bit: 0..65535 (CD)

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

Page 24: NoiseGen at Arlington Ruby 2012

Human Ear: 21 bit @ 40k

Page 25: NoiseGen at Arlington Ruby 2012

Volume (amplitude) and Frequency

Page 26: NoiseGen at Arlington Ruby 2012

Volume is easy!

Page 27: NoiseGen at Arlington Ruby 2012

Frequency not so easy

Page 28: NoiseGen at Arlington Ruby 2012

frequency = wiggle speed = pitch

Page 29: NoiseGen at Arlington Ruby 2012
Page 30: NoiseGen at Arlington Ruby 2012

Low bit depthcauses volume-aliasing

Page 31: NoiseGen at Arlington Ruby 2012

Low sample ratecauses frequency-aliasing

Page 32: NoiseGen at Arlington Ruby 2012
Page 33: NoiseGen at Arlington Ruby 2012

So!

Page 34: NoiseGen at Arlington Ruby 2012

We just gotta generate and play samples!

Page 35: NoiseGen at Arlington Ruby 2012

44100 of them per second!

Page 36: NoiseGen at Arlington Ruby 2012

No problem.

Page 37: NoiseGen at Arlington Ruby 2012

PortAudio

Page 38: NoiseGen at Arlington Ruby 2012

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

Page 39: NoiseGen at Arlington Ruby 2012

PortAudio.init

stream = PortAudio::Stream.open( :sample_rate => 44100, :frames => 512, :output => { :device => PortAudio::Device.default_output, :channels => 1, :sample_format => :float32 })

stream.start

Page 40: NoiseGen at Arlington Ruby 2012

buffer = PortAudio::SampleBuffer.new( :format => :float32, :channels => 1, :frames => 512)

Page 41: NoiseGen at Arlington Ruby 2012

# smallnoise.rb

loop do buffer.fill { rand()*2 - 1 # From -1 .. 1 } stream << bufferend

Page 42: NoiseGen at Arlington Ruby 2012

Woo... static!

Page 43: NoiseGen at Arlington Ruby 2012

sample = rand()*2 - 1

Page 44: NoiseGen at Arlington Ruby 2012

# smallbeep.rb

time_step = 1 / 48000.0time = 0.0loop do buffer.fill { time += time_step; Math.sin( 2 * 3.1415 * time * 440 ); } stream << bufferend

Page 45: NoiseGen at Arlington Ruby 2012

Woo... beeping!

Page 46: NoiseGen at Arlington Ruby 2012

Two beeps at once?

Page 47: NoiseGen at Arlington Ruby 2012

Turns out you just add waves together

Page 48: NoiseGen at Arlington Ruby 2012

# smallbeep2.rb

sample = Math.sin( 2 * 3.1415 * time * 440 );sample += Math.sin( 2 * 3.1415 * time * 349.23 );sample /= 2; # Avoid clipping!

Page 49: NoiseGen at Arlington Ruby 2012

Let's generalize

Page 50: NoiseGen at Arlington Ruby 2012

sample = get_next_sample();

Page 51: NoiseGen at Arlington Ruby 2012

Call get_next_sample() over and over

Page 52: NoiseGen at Arlington Ruby 2012

Get a new sample each time

Page 53: NoiseGen at Arlington Ruby 2012

def sine { Math.sin( 2 * 3.1415 * $time * 440 )}

Page 54: NoiseGen at Arlington Ruby 2012

That was easy.

Page 55: NoiseGen at Arlington Ruby 2012

Not very configurable or dynamic though.

Page 56: NoiseGen at Arlington Ruby 2012

# What I want:

sample_gen = sine(440);

# ...

sample = sample_gen.call;

Page 57: NoiseGen at Arlington Ruby 2012

This is called a 'generator'

Page 58: NoiseGen at Arlington Ruby 2012

We can make this using a closure!

Page 59: NoiseGen at Arlington Ruby 2012

(aka lambda with bound variables)

Page 60: NoiseGen at Arlington Ruby 2012

def sine(freq) lambda { Math.sin( 2 * 3.1415 * $time * freq ); }}

Page 61: NoiseGen at Arlington Ruby 2012

# So now we have it:

sample_gen = sine(440);

# ... in 'play'

sample = sample_gen.call;

Page 62: NoiseGen at Arlington Ruby 2012

# Create a 440 Hz sine generatorgen = sine(440);

# Play it!play( gen );

Page 63: NoiseGen at Arlington Ruby 2012

play( sine( 440 ) );

Page 64: NoiseGen at Arlington Ruby 2012

One more generator tweak

Page 65: NoiseGen at Arlington Ruby 2012

Parameterize generators with generators,and use named params

Page 66: NoiseGen at Arlington Ruby 2012

sub sine(freq) { freq = genize freq lambda { Math.sin( 2 * 3.1415 * $time * freq.call ); }}

Page 67: NoiseGen at Arlington Ruby 2012

# Take a parameter and ensure it is a generator.# If it is already a generator leave it alone,# otherwise wrap it up so that it *is* a generator.

def genize x if x.is_a?(Proc) return x end lambda { x }end

Page 68: NoiseGen at Arlington Ruby 2012

Why would we use generatorsas parameters?

Page 69: NoiseGen at Arlington Ruby 2012

lfo = sine(5)wobble_freq = lambda { lfo.call * 100 + 440 }

play( sine( wobble_freq ) );

Page 70: NoiseGen at Arlington Ruby 2012

Now we're cooking with FIRE!

Page 71: NoiseGen at Arlington Ruby 2012

Plays forever...

Page 72: NoiseGen at Arlington Ruby 2012

Envelope Generator

Page 73: NoiseGen at Arlington Ruby 2012

Attack, [Decay], Sustain, Release

Page 74: NoiseGen at Arlington Ruby 2012
Page 75: NoiseGen at Arlington Ruby 2012

envelope( gen, attack, sustain, release )

# For example

envelope( sine(440), 2, 0, 2 )

Page 76: NoiseGen at Arlington Ruby 2012

returns nil when done

Page 77: NoiseGen at Arlington Ruby 2012

Sequence Generator

Page 78: NoiseGen at Arlington Ruby 2012

seq( gens )

# For example

play( seq([ envelope(square(440), 2, 0, 2), envelope(square(220), 2, 0, 2), ]))

Page 79: NoiseGen at Arlington Ruby 2012

Simultaneous Generators Generator

Page 80: NoiseGen at Arlington Ruby 2012

play( sum([ envelope(square(440), 2, 0, 2), envelope(square(220), 2, 0, 2), envelope(square(880), 2, 0, 2), envelope(square(660), 2, 0, 2), ]))

Page 81: NoiseGen at Arlington Ruby 2012

Let's build something we can PLAY

Page 82: NoiseGen at Arlington Ruby 2012

Input Control Generator

Page 83: NoiseGen at Arlington Ruby 2012

Using my touch-screen

Page 84: NoiseGen at Arlington Ruby 2012

$ xmousepos838 574 221 170

Page 85: NoiseGen at Arlington Ruby 2012

x = `xmousepos`.split[0];

Page 86: NoiseGen at Arlington Ruby 2012

def mousefreq() count = 0 x = 0.0 lambda { count += 1 if count % 1000 == 0 x = `xmousepos`.split[0].to_f end x }end

Page 87: NoiseGen at Arlington Ruby 2012

play( amp( sine( mousefreq() ), mousevol() ));

Page 88: NoiseGen at Arlington Ruby 2012

And now we have a synth :)

Page 89: NoiseGen at Arlington Ruby 2012

THE END

Page 90: NoiseGen at Arlington Ruby 2012

References

Source Code:http://thelackthereof.org/NoiseGenhttp://github.com/awwaiid/ruby-noise

Digital fidelity discussion:http://people.xiph.org/~xiphmont/demo/neil-young.html

Page 91: NoiseGen at Arlington Ruby 2012

BONUS SLIDES

Page 92: NoiseGen at Arlington Ruby 2012

Note / Song Generators

Page 93: NoiseGen at Arlington Ruby 2012

note( 'A4' )

Page 94: NoiseGen at Arlington Ruby 2012

segment( 'A4 F3' ),

Page 95: NoiseGen at Arlington Ruby 2012

combine([ segment( 'A4' ), segment( 'F3' ),])

Page 96: NoiseGen at Arlington Ruby 2012

Formula-Based Noisehttp://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html