Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby, Rock and Roll

Ruby, Rock and Roll

In which I talked about Muse, the pure Ruby music synthesizing gem.

Ee191858f0d96ad93098694537f71998?s=128

Sau Sheong Chang

June 23, 2013
Tweet

Transcript

  1. Ruby, Rock and Roll Chang Sau Sheong @sausheong June 2013

    Monday, 24 June, 13
  2. About

  3. About

  4. About

  5. About

  6. About

  7. About

  8. About

  9. About

  10. About

  11. About

  12. Muse http://github.com/sausheong/muse Monday, 24 June, 13

  13. A

  14. Monday, 24 June, 13

  15. Monday, 24 June, 13

  16. Creating

  17. About

  18. cycle amplitude Higher

  19. Make

  20. The

  21. 1

  22. The

  23. 440Hz 880Hz 1760Hz 3520Hz A4 A5 A6 A7 A A

    A A Monday, 24 June, 13
  24. difference

  25. •Get

  26. •To

  27. Get

  28. NOTES = %w(a ais b c cis d dis e

    f fis g gis) FREQUENCIES = { d4:-7, dis4:-6, e4:-5, f4: -4, fis4:-3, g4:-2, gis4:-1, a4: 0, ais4: 1, b4: 2, c5: 3, cis5: 4, d5: 5 } # get the frequency of the pitch def frequency_of(step) 440.0*(2**(step.to_f/12.0)) end sample_rate = 44100.0 # 44100 Hz duration = 1 # 1 sec stream = [] # data stream for left and right channels frequency = frequency_of(FREQUENCIES[:a4]) # for the duration of 1 sec, step every 1/44100 times and # write the value (0.0..duration.to_f).step(1/sample_rate) do |i| x = (10000 * Math.sin(2 * Math::PI * frequency * i)).to_i stream << [x,x] end Monday, 24 June, 13
  29. [[0, 0], [626, 626], [1250, 1250], [1869, 1869], [2481, 2481],

    [3083, 3083], [3673, 3673], [4248, 4248], [4807, 4807], [5347, 5347], [5866, 5866], [6362, 6362], [6832, 6832], [7276, 7276], [7692, 7692], [8077, 8077], [8431, 8431], [8751, 8751], [9037, 9037], [9287, 9287], [9501, 9501], [9678, 9678], [9816, 9816], [9916, 9916], [9978, 9978], [9999, 9999], [9982, 9982], [9925, 9925], [9830, 9830], [9696, 9696], [9523, 9523], [9313, 9313], [9067, 9067], [8785, 8785], [8469, 8469], [8119, 8119], [7737, 7737], [7325, 7325], [6884, 6884], [6416, 6416], [5923, 5923], [5407, 5407], [4869, 4869], [4313, 4313], [3739, 3739], [3151, 3151], [2550, 2550], [1939, 1939], [1321, 1321], [697, 697], [71, 71], [-555, -555], [-1179, -1179], [-1799, -1799], [-2412, -2412], [-3015, -3015], [-3606, -3606], [-4184, -4184], [-4744, -4744], [-5287, -5287], [-5808, -5808], [-6307, -6307], [-6780, -6780], [-7227, -7227], [-7646, -7646], [-8035, -8035], [-8392, -8392], [-8716, -8716], [-9006, -9006], [-9261, -9261], [-9479, -9479], [-9660, -9660], [-9803, -9803], [-9907, -9907], [-9973, -9973], [-9999, -9999], [-9986, -9986], [-9934, -9934], [-9843, -9843], [-9713, -9713], [-9545, -9545], [-9339, -9339], [-9097, -9097], [-8819, -8819], [-8506, -8506], [-8160, -8160], [-7782, -7782], [-7373, -7373], [-6936, -6936], [-6471, -6471], [-5981, -5981], [-5467, -5467], [-4931, -4931], [-4377, -4377], [-3805, -3805], [-3218, -3218], [-2619, -2619], [-2009, -2009], [-1391, -1391], [-768, -768], [-142, -142], [484, 484], [1109, 1109], [1729, 1729], [2343, 2343], [2947, 2947], [3540, 3540], [4119, 4119], [4682, 4682], [5226, 5226], [5750, 5750], [6251, 6251], [6728, 6728], [7178, 7178], [7600, 7600], [7992, 7992], [8353, 8353], [8681, 8681], [8975, 8975], [9234, 9234], [9456, 9456], [9641, 9641], [9788, 9788], [9897, 9897], [9967, 9967], [9998, 9998], [9989, 9989], [9942, 9942], [9855, 9855], [9729, 9729], [9566, 9566], [9364, 9364], [9126, 9126], [8852, 8852], [8544, 8544], [8201, 8201], [7827, 7827], [7421, 7421], [6987, 6987], [6525, 6525], [6038, 6038], [5526, 5526], [4993, 4993], [4441, 4441], [3871, 3871], [3285, 3285], [2687, 2687], [2079, 2079], [1462, 1462], [839, 839], [213, 213], [-413, -413], [-1038, -1038], [-1659, -1659], [-2273, -2273], [-2879, -2879], [-3473, -3473], [-4054, -4054], [-4619, -4619], [-5165, -5165], [-5691, -5691], [-6195, -6195], [-6675, -6675], [-7128, -7128], [-7554, -7554], [-7949, -7949], [-8314, -8314], [-8645, -8645], [-8943, -8943], [-9206, -9206], [-9432, -9432], [-9622, -9622], [-9774, -9774], [-9887, -9887], [-9961, -9961], [-9996, -9996], [-9992, -9992], [-9949, -9949], [-9867, -9867], [-9746, -9746], [-9586, -9586], [-9389, -9389], [-9155, -9155], [-8885, -8885], [-8580, -8580], [-8242, -8242], [-7871, -7871], [-7469, -7469], [-7038, -7038], [-6579, -6579], [-6094, -6094], [-5586, -5586], [-5055, -5055], [-4504, -4504], ...] stream Monday, 24 June, 13
  30. [[0, 0], [626, 626], [1250, 1250], [1869, 1869], [2481, 2481],

    [3083, 3083], [3673, 3673], [4248, 4248], [4807, 4807], [5347, 5347], [5866, 5866], [6362, 6362], [6832, 6832], [7276, 7276], [7692, 7692], [8077, 8077], [8431, 8431], [8751, 8751], [9037, 9037], [9287, 9287], [9501, 9501], [9678, 9678], [9816, 9816], [9916, 9916], [9978, 9978], [9999, 9999], [9982, 9982], [9925, 9925], [9830, 9830], [9696, 9696], [9523, 9523], [9313, 9313], [9067, 9067], [8785, 8785], [8469, 8469], [8119, 8119], [7737, 7737], [7325, 7325], [6884, 6884], [6416, 6416], [5923, 5923], [5407, 5407], [4869, 4869], [4313, 4313], [3739, 3739], [3151, 3151], [2550, 2550], [1939, 1939], [1321, 1321], [697, 697], [71, 71], [-555, -555], [-1179, -1179], [-1799, -1799], [-2412, -2412], [-3015, -3015], [-3606, -3606], [-4184, -4184], [-4744, -4744], [-5287, -5287], [-5808, -5808], [-6307, -6307], [-6780, -6780], [-7227, -7227], [-7646, -7646], [-8035, -8035], [-8392, -8392], [-8716, -8716], [-9006, -9006], [-9261, -9261], [-9479, -9479], [-9660, -9660], [-9803, -9803], [-9907, -9907], [-9973, -9973], [-9999, -9999], [-9986, -9986], [-9934, -9934], [-9843, -9843], [-9713, -9713], [-9545, -9545], [-9339, -9339], [-9097, -9097], [-8819, -8819], [-8506, -8506], [-8160, -8160], [-7782, -7782], [-7373, -7373], [-6936, -6936], [-6471, -6471], [-5981, -5981], [-5467, -5467], [-4931, -4931], [-4377, -4377], [-3805, -3805], [-3218, -3218], [-2619, -2619], [-2009, -2009], [-1391, -1391], [-768, -768], [-142, -142], [484, 484], [1109, 1109], [1729, 1729], [2343, 2343], [2947, 2947], [3540, 3540], [4119, 4119], [4682, 4682], [5226, 5226], [5750, 5750], [6251, 6251], [6728, 6728], [7178, 7178], [7600, 7600], [7992, 7992], [8353, 8353], [8681, 8681], [8975, 8975], [9234, 9234], [9456, 9456], [9641, 9641], [9788, 9788], [9897, 9897], [9967, 9967], [9998, 9998], [9989, 9989], [9942, 9942], [9855, 9855], [9729, 9729], [9566, 9566], [9364, 9364], [9126, 9126], [8852, 8852], [8544, 8544], [8201, 8201], [7827, 7827], [7421, 7421], [6987, 6987], [6525, 6525], [6038, 6038], [5526, 5526], [4993, 4993], [4441, 4441], [3871, 3871], [3285, 3285], [2687, 2687], [2079, 2079], [1462, 1462], [839, 839], [213, 213], [-413, -413], [-1038, -1038], [-1659, -1659], [-2273, -2273], [-2879, -2879], [-3473, -3473], [-4054, -4054], [-4619, -4619], [-5165, -5165], [-5691, -5691], [-6195, -6195], [-6675, -6675], [-7128, -7128], [-7554, -7554], [-7949, -7949], [-8314, -8314], [-8645, -8645], [-8943, -8943], [-9206, -9206], [-9432, -9432], [-9622, -9622], [-9774, -9774], [-9887, -9887], [-9961, -9961], [-9996, -9996], [-9992, -9992], [-9949, -9949], [-9867, -9867], [-9746, -9746], [-9586, -9586], [-9389, -9389], [-9155, -9155], [-8885, -8885], [-8580, -8580], [-8242, -8242], [-7871, -7871], [-7469, -7469], [-7038, -7038], [-6579, -6579], [-6094, -6094], [-5586, -5586], [-5055, -5055], [-4504, -4504], ...] stream Monday, 24 June, 13
  31. Harmonics •Sine

  32. y=sin(x) y

  33. def harmonics(input) Math.sin(2 * Math::PI * input) + Math.sin(2 *

    Math::PI * input * 2) end # for the duration of 1 sec, step every 1/44100 times and # write the value (0.0..duration.to_f).step(1/sample_rate) do |i| x = (10000 * harmonics(frequency * i)).to_i stream << [x,x] end Monday, 24 June, 13
  34. def harmonics(input) Math.sin(2 * Math::PI * input) + Math.sin(2 *

    Math::PI * input * 2) end # for the duration of 1 sec, step every 1/44100 times and # write the value (0.0..duration.to_f).step(1/sample_rate) do |i| x = (10000 * harmonics(frequency * i)).to_i stream << [x,x] end Monday, 24 June, 13
  35. Envelopes •Changing

  36. input

  37. input

  38. input

  39. Digital

  40. The

  41. Monday, 24 June, 13

  42. Monday, 24 June, 13

  43. Create

  44. require 'bindata' class RiffChunk < BinData::Record int32be :chunk_id int32le :chunk_size

    int32be :format end class FormatChunk < BinData::Record int32be :chunk_id int32le :chunk_size int16le :audio_format int16le :num_channels int32le :sample_rate int32le :byte_rate int16le :block_align int16le :bits_per_sample end class DataChunk < BinData::Record int32be :chunk_id int32le :chunk_size array :stream do int16le :left int16le :right end end class WavFormat < BinData::Record riff_chunk :riff_chunk format_chunk :format_chunk data_chunk :data_chunk end Monday, 24 June, 13
  45. class Wav SAMPLE_RATE = 44100 attr :wav, :file, :sample_rate, :format_chunk,

    :riff_chunk, :data_chunk def initialize(filename) @sample_rate = SAMPLE_RATE @file = File.open(filename, "wb") @riff_chunk = RiffChunk.new @riff_chunk.chunk_id = "RIFF".unpack("N").first @riff_chunk.format = "WAVE".unpack("N").first @format_chunk = FormatChunk.new @format_chunk.chunk_id = "fmt ".unpack("N").first @format_chunk.chunk_size = 16 @format_chunk.audio_format = 1 @format_chunk.num_channels = 2 @format_chunk.bits_per_sample = 16 @format_chunk.sample_rate = @sample_rate @format_chunk.byte_rate = @format_chunk.sample_rate * @format_chunk.num_channels * @format_chunk.bits_per_sample/2 @format_chunk.block_align = @format_chunk.num_channels * @format_chunk.bits_per_sample/2 @data_chunk = DataChunk.new @data_chunk.chunk_id = "data".unpack("N").first end Monday, 24 June, 13
  46. def write(stream_data) stream_data.each_with_index do |s,i| @data_chunk.stream[i].left = s[0] @data_chunk.stream[i].right =

    s[1] end @data_chunk.chunk_size = stream_data.length * @format_chunk.num_channels * @format_chunk.bits_per_sample/8 @riff_chunk.chunk_size = 36 + @data_chunk.chunk_size @wav = WavFormat.new @wav.riff_chunk = @riff_chunk @wav.format_chunk = @format_chunk @wav.data_chunk = @data_chunk @wav.write(@file) end def close @file.close end end wav_file = Wav.new('sine.wav') wav_file.write(stream) wav_file.close Monday, 24 June, 13
  47. Putting

  48. NOTES = %w(a ais b c cis d dis e

    f fis g gis) FREQUENCIES = { d4:-7, dis4:-6, e4:-5, f4:-4, fis4:-3, g4:-2, gis4:-1, a4: 0, ais4: 1, b4: 2, c5: 3, cis5: 4, d5: 5 } # get the frequency of the pitch def frequency_of(step) 440.0*(2**(step.to_f/12.0)) end sample_rate = 44100.0 # 44100 Hz duration = 1 # 1 sec stream = [] # data stream for left and right channels frequency = frequency_of(FREQUENCIES[:a4]) # for the duration of 1 sec, step every 1/44100 times and # write the value (0.0..duration.to_f).step(1/sample_rate) do |i| x = (10000 * Math.sin(2 * Math::PI * frequency * i)).to_i stream << [x,x] end wav_file = Wav.new('sine.wav') wav_file.write(stream) wav_file.close Monday, 24 June, 13
  49. Domain

  50. Techniques

  51. DSL

  52. instance_eval class Bar ... def notes(&block) instance_eval &block end end

    require "muse" include Muse Song.record 'hotel_california', harmonic: 'guitar', bpm: 100 do bar(1).notes { d4 b:2; e4; fis4;} ... end Monday, 24 June, 13
  53. method_missing class Bar ... def method_missing(name, *args, &block) name =

    name.to_s if name.start_with? *NOTES ... end end end require "muse" include Muse Song.record 'hotel_california', harmonic: 'guitar', bpm: 100 do ... bar(3).notes { a3_d4_fis4; fis4; fis4 b:0.5;} ... end Monday, 24 June, 13
  54. Turk!h March Monday, 24 June, 13

  55. Wait,

  56. Otto the Algorithmic Composer Monday, 24 June, 13

  57. Algorithmic composition is the technique of using algorithms to create

    music - Wikipedia Monday, 24 June, 13
  58. Grab tweet from Twitter, create music from it Monday, 24

    June, 13
  59. Grab tweet from Twitter, create music from it Scan a

    picture Monday, 24 June, 13
  60. Grab tweet from Twitter, create music from it Scan a

    picture Get today’s weather report Monday, 24 June, 13
  61. The algorithm Monday, 24 June, 13

  62. Melody + Chords = Music Monday, 24 June, 13

  63. Grab tweet from Twitter text = Twitter.search("#rubyconfindia").statuses.last.text Monday, 24 June,

    13
  64. Split tweet into words words = text.split Monday, 24 June,

    13
  65. Convert each word into a number using the touch tone

    keypad algorithm Monday, 24 June, 13
  66. Monday, 24 June, 13

  67. KEYPAD = {a:2,b:2,c:2, d:3,e:3,f:3, g:4,h:4,i:4, j:5,k:5,l:5, m:6,n:6,o:6, p:7,q:7,r:7,s:7, t:8,u:8,v:8, w:9,x:9,y:9,z:9}

    def word_to_num(a_word) a_word.downcase.chars.inject("") { |memo,obj| memo + KEYPAD[obj.to_sym].to_s } end Monday, 24 June, 13
  68. Find the 7 most frequently found notes music = []

    words.each do |w| music << NOTES[note(w)] end sorted = music.counts.sort_by {|obj| obj[1]}.reverse most_frequent_7_notes = sorted.map {|obj| obj[0]}.first(7) Monday, 24 June, 13
  69. Determine the musical scale from the 7 notes MAJOR =

    { 0 => %w(c d e f g a b c d), 1 => %w(g a b c d e fis g a), 2 => %w(d e fis g a b cis d e), 3 => %w(a b cis d e fis gis a b), 4 => %w(e fis gis a b cis dis e fis), 5 => %w(b cis dis e fis gis ais b cis), 6 => %w(fis gis ais b cis dis f fis gis) } num_of_sharps = most_frequent_7_notes.inject(0) { |memo, obj| obj.end_with? ("is") ? memo + 1 : memo } scale = MAJOR[num_of_sharps] Monday, 24 June, 13
  70. Get the chord progression for the scale (using I-IV-V) scale_chords

    = {} scale_chords[0] = ["#{scale[0]}", "#{scale[2]}", "#{scale[4]}"] scale_chords[1] = ["#{scale[3]}", "#{scale[5]}", "#{scale[7]}"] scale_chords[2] = ["#{scale[4]}", "#{scale[6]}", "#{scale[8]}"] Monday, 24 June, 13
  71. Melody + Chords = Music ✓ Monday, 24 June, 13

  72. Start with the first note of the scale Monday, 24

    June, 13
  73. Calculate the ‘distance’ between the current note and the next

    def distance(a_word) word_to_num(a_word).to_i % 5 end Monday, 24 June, 13
  74. Determine if the next note goes ‘up’ or ‘down’ the

    scale def direction(a_word) (word_to_num(a_word).to_i % 2) == 0 ? 1 : -1 end Monday, 24 June, 13
  75. Get number of syllables for each word, each syllable is

    1 beat def syllables(a_word) word = a_word.downcase return 1 if word.length <= 3 word.sub!(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, '') word.sub!(/^y/, '') word.scan(/[aeiouy]{1,2}/).size end Monday, 24 June, 13
  76. Move from current note to next note if direction(word) >

    0 bar_notes << scale.next(distance(word)) else bar_notes << scale.previous(distance(word)) end Monday, 24 June, 13
  77. Melody + Chords = Music ✓ ✓ Monday, 24 June,

    13
  78. Break down into 4 words per bar, use Muse to

    write to WAV file Monday, 24 June, 13
  79. quadruplets.reverse.each_with_index do |quad, index| beats = 0.0 bar(index).notes { beats,

    bar_notes = [], [] quad.each do |word| beats << syllables(word).to_f/2 if direction(word) > 0 bar_notes << scale.next(distance(word)) else bar_notes << scale.previous(distance(word)) end end total_beats = beats.inject(:+) bar_notes.each_with_index do |n,i| note = n.chop octave = n[n.size-1] b = (beats[i]*4.0)/total_beats add_to_stream note_data(note, octave, b:b) end } bar(index, v:1.5).notes { ch = scale_chords[index % 3] chord = [ch[1], ch[2], ch[0]] add_to_stream note_data(ch[0].chop, 3, b:1) add_to_stream chord(chord, b:1) add_to_stream chord(chord, b:1) add_to_stream chord(chord, b:1) } Monday, 24 June, 13
  80. Demo Monday, 24 June, 13

  81. @sausheong sausheong@gmail.com Thank you for listening! Monday, 24 June, 13