Successfully reported this slideshow.
noise.rbHow to annoy your cat with sound generatorsArlington Ruby, May 2012Brock Wilcoxawwaiid@thelackthereof.org
Lets build a software synthesizerin about 20 40 minutes.
Digital Sound
Exercises for reader:Speakers → Ears → Consciousness
Lets start with speakers
Sideways is even better
Alternating Current
Sound = Speaker movement = Electrical input
Waves
digital?
Divide into samples
A sample is number from -1 .. 1
(y-axis)
Evenly spaced over time
(x-axis)
Samples per second↳ "Sample Rate"
Common sample rates:DVD: 48,000CD: 44,100VOIP: 16,000Telephone: 8,000
Representation of each sample↳ "Bit Depth"
8 bit: 0..255 (old video games)16 bit: 0..65535 (CD)24 bit: 0..16777215 (DVD-Audio)
Human Ear: 21 bit @ 40k
Volume (amplitude) and Frequency
Volume is easy!
Frequency not so easy
frequency = wiggle speed = pitch
Low bit depthcauses volume-aliasing
Low sample ratecauses frequency-aliasing
So!
We just gotta generate and play samples!
44100 of them per second!
No problem.
PortAudio
Alternatives: /dev/dsp, aplay, ...
PortAudio.initstream = PortAudio::Stream.open(  :sample_rate => 44100,  :frames      => 512,  :output      => {    :device...
buffer = PortAudio::SampleBuffer.new(  :format   => :float32,  :channels => 1,  :frames   => 512)
# smallnoise.rbloop do  buffer.fill {    rand()*2 - 1 # From -1 .. 1  }  stream << bufferend
Woo... static!
sample = rand()*2 - 1
# smallbeep.rbtime_step = 1 / 48000.0time = 0.0loop do  buffer.fill {    time += time_step;    Math.sin( 2 * 3.1415 * time...
Woo... beeping!
Two beeps at once?
Turns out you just add waves together
# smallbeep2.rbsample = Math.sin( 2 * 3.1415 * time * 440 );sample += Math.sin( 2 * 3.1415 * time * 349.23 );sample /= 2; ...
Lets generalize
sample = get_next_sample();
Call get_next_sample() over and over
Get a new sample each time
def sine {  Math.sin( 2 * 3.1415 * $time * 440 )}
That was easy.
Not very configurable or dynamic though.
# What I want:sample_gen = sine(440);# ...sample = sample_gen.call;
This is called a generator
We can make this using a closure!
(aka lambda with bound variables)
def sine(freq)  lambda {    Math.sin( 2 * 3.1415 * $time * freq );  }}
# So now we have it:sample_gen = sine(440);# ... in playsample = sample_gen.call;
# Create a 440 Hz sine generatorgen = sine(440);# Play it!play( gen );
play( sine( 440 ) );
One more generator tweak
Parameterize generators with generators,and use named params
sub sine(freq) {  freq = genize freq  lambda {    Math.sin( 2 * 3.1415 * $time * freq.call );  }}
# Take a parameter and ensure it is a generator.# If it is already a generator leave it alone,# otherwise wrap it up so th...
Why would we use generatorsas parameters?
lfo = sine(5)wobble_freq = lambda { lfo.call * 100 + 440 }play( sine( wobble_freq ) );
Now were cooking with FIRE!
Plays forever...
Envelope Generator
Attack, [Decay], Sustain, Release
envelope( gen, attack, sustain, release )# For exampleenvelope( sine(440), 2, 0, 2 )
returns nil when done
Sequence Generator
seq( gens )# For exampleplay(  seq([     envelope(square(440), 2, 0, 2),     envelope(square(220), 2, 0, 2),  ]))
Simultaneous Generators Generator
play(  sum([     envelope(square(440),   2,   0,   2),     envelope(square(220),   2,   0,   2),     envelope(square(880),...
Lets build something we can PLAY
Input Control Generator
Using my touch-screen
$ xmousepos838 574 221 170
x = `xmousepos`.split[0];
def mousefreq()  count = 0  x = 0.0  lambda {    count += 1    if count % 1000 == 0      x = `xmousepos`.split[0].to_f    ...
play(   amp(     sine( mousefreq() ),     mousevol()   ));
And now we have a synth :)
THE END
ReferencesSource Code:http://thelackthereof.org/NoiseGenhttp://github.com/awwaiid/ruby-noiseDigital fidelity discussion:htt...
BONUS SLIDES
Note / Song Generators
note( A4 )
segment( A4 F3 ),
combine([   segment( A4 ),   segment( F3 ),])
Formula-Based Noisehttp://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
NoiseGen at Arlington Ruby 2012
Upcoming SlideShare
Loading in …5
×

NoiseGen at Arlington Ruby 2012

746 views

Published on

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

Published in: Technology, Business
  • Be the first to comment

  • Be the first to like this

NoiseGen at Arlington Ruby 2012

  1. 1. noise.rbHow to annoy your cat with sound generatorsArlington Ruby, May 2012Brock Wilcoxawwaiid@thelackthereof.org
  2. 2. Lets build a software synthesizerin about 20 40 minutes.
  3. 3. Digital Sound
  4. 4. Exercises for reader:Speakers → Ears → Consciousness
  5. 5. Lets start with speakers
  6. 6. Sideways is even better
  7. 7. Alternating Current
  8. 8. Sound = Speaker movement = Electrical input
  9. 9. Waves
  10. 10. digital?
  11. 11. Divide into samples
  12. 12. A sample is number from -1 .. 1
  13. 13. (y-axis)
  14. 14. Evenly spaced over time
  15. 15. (x-axis)
  16. 16. Samples per second↳ "Sample Rate"
  17. 17. Common sample rates:DVD: 48,000CD: 44,100VOIP: 16,000Telephone: 8,000
  18. 18. Representation of each sample↳ "Bit Depth"
  19. 19. 8 bit: 0..255 (old video games)16 bit: 0..65535 (CD)24 bit: 0..16777215 (DVD-Audio)
  20. 20. Human Ear: 21 bit @ 40k
  21. 21. Volume (amplitude) and Frequency
  22. 22. Volume is easy!
  23. 23. Frequency not so easy
  24. 24. frequency = wiggle speed = pitch
  25. 25. Low bit depthcauses volume-aliasing
  26. 26. Low sample ratecauses frequency-aliasing
  27. 27. So!
  28. 28. We just gotta generate and play samples!
  29. 29. 44100 of them per second!
  30. 30. No problem.
  31. 31. PortAudio
  32. 32. Alternatives: /dev/dsp, aplay, ...
  33. 33. PortAudio.initstream = PortAudio::Stream.open( :sample_rate => 44100, :frames => 512, :output => { :device => PortAudio::Device.default_output, :channels => 1, :sample_format => :float32 })stream.start
  34. 34. buffer = PortAudio::SampleBuffer.new( :format => :float32, :channels => 1, :frames => 512)
  35. 35. # smallnoise.rbloop do buffer.fill { rand()*2 - 1 # From -1 .. 1 } stream << bufferend
  36. 36. Woo... static!
  37. 37. sample = rand()*2 - 1
  38. 38. # smallbeep.rbtime_step = 1 / 48000.0time = 0.0loop do buffer.fill { time += time_step; Math.sin( 2 * 3.1415 * time * 440 ); } stream << bufferend
  39. 39. Woo... beeping!
  40. 40. Two beeps at once?
  41. 41. Turns out you just add waves together
  42. 42. # smallbeep2.rbsample = Math.sin( 2 * 3.1415 * time * 440 );sample += Math.sin( 2 * 3.1415 * time * 349.23 );sample /= 2; # Avoid clipping!
  43. 43. Lets generalize
  44. 44. sample = get_next_sample();
  45. 45. Call get_next_sample() over and over
  46. 46. Get a new sample each time
  47. 47. def sine { Math.sin( 2 * 3.1415 * $time * 440 )}
  48. 48. That was easy.
  49. 49. Not very configurable or dynamic though.
  50. 50. # What I want:sample_gen = sine(440);# ...sample = sample_gen.call;
  51. 51. This is called a generator
  52. 52. We can make this using a closure!
  53. 53. (aka lambda with bound variables)
  54. 54. def sine(freq) lambda { Math.sin( 2 * 3.1415 * $time * freq ); }}
  55. 55. # So now we have it:sample_gen = sine(440);# ... in playsample = sample_gen.call;
  56. 56. # Create a 440 Hz sine generatorgen = sine(440);# Play it!play( gen );
  57. 57. play( sine( 440 ) );
  58. 58. One more generator tweak
  59. 59. Parameterize generators with generators,and use named params
  60. 60. sub sine(freq) { freq = genize freq lambda { Math.sin( 2 * 3.1415 * $time * freq.call ); }}
  61. 61. # 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
  62. 62. Why would we use generatorsas parameters?
  63. 63. lfo = sine(5)wobble_freq = lambda { lfo.call * 100 + 440 }play( sine( wobble_freq ) );
  64. 64. Now were cooking with FIRE!
  65. 65. Plays forever...
  66. 66. Envelope Generator
  67. 67. Attack, [Decay], Sustain, Release
  68. 68. envelope( gen, attack, sustain, release )# For exampleenvelope( sine(440), 2, 0, 2 )
  69. 69. returns nil when done
  70. 70. Sequence Generator
  71. 71. seq( gens )# For exampleplay( seq([ envelope(square(440), 2, 0, 2), envelope(square(220), 2, 0, 2), ]))
  72. 72. Simultaneous Generators Generator
  73. 73. 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), ]))
  74. 74. Lets build something we can PLAY
  75. 75. Input Control Generator
  76. 76. Using my touch-screen
  77. 77. $ xmousepos838 574 221 170
  78. 78. x = `xmousepos`.split[0];
  79. 79. def mousefreq() count = 0 x = 0.0 lambda { count += 1 if count % 1000 == 0 x = `xmousepos`.split[0].to_f end x }end
  80. 80. play( amp( sine( mousefreq() ), mousevol() ));
  81. 81. And now we have a synth :)
  82. 82. THE END
  83. 83. ReferencesSource Code:http://thelackthereof.org/NoiseGenhttp://github.com/awwaiid/ruby-noiseDigital fidelity discussion:http://people.xiph.org/~xiphmont/demo/neil-young.html
  84. 84. BONUS SLIDES
  85. 85. Note / Song Generators
  86. 86. note( A4 )
  87. 87. segment( A4 F3 ),
  88. 88. combine([ segment( A4 ), segment( F3 ),])
  89. 89. Formula-Based Noisehttp://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html

×