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.

Avatar for Sau Sheong Chang

Sau Sheong Chang

June 23, 2013
Tweet

More Decks by Sau Sheong Chang

Other Decks in Technology

Transcript

  1. A

  2. The

  3. 1

  4. The

  5. Get

  6. 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
  7. [[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
  8. [[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
  9. 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
  10. 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
  11. The

  12. 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
  13. 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
  14. 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
  15. 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
  16. DSL

  17. 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
  18. 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
  19. Grab tweet from Twitter, create music from it Scan a

    picture Get today’s weather report Monday, 24 June, 13
  20. Convert each word into a number using the touch tone

    keypad algorithm Monday, 24 June, 13
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. Break down into 4 words per bar, use Muse to

    write to WAV file Monday, 24 June, 13
  30. 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