Sau Sheong Chang
June 23, 2013
270

# Ruby, Rock and Roll

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

June 23, 2013

## Transcript

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

Monday, 24 June, 13

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

A A Monday, 24 June, 13

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

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

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

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

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

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

music - Wikipedia Monday, 24 June, 13

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

13

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

keypad algorithm 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

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

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

write to WAV ﬁle 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