Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

NoiseGen at Arlington Ruby 2012

782 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

×