Slide 1

Slide 1 text

Ruby, Rock and Roll Chang Sau Sheong @sausheong June 2013 Monday, 24 June, 13

Slide 2

Slide 2 text

About

Slide 3

Slide 3 text

About

Slide 4

Slide 4 text

About

Slide 5

Slide 5 text

About

Slide 6

Slide 6 text

About

Slide 7

Slide 7 text

About

Slide 8

Slide 8 text

About

Slide 9

Slide 9 text

About

Slide 10

Slide 10 text

About

Slide 11

Slide 11 text

About

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

A

Slide 14

Slide 14 text

Monday, 24 June, 13

Slide 15

Slide 15 text

Monday, 24 June, 13

Slide 16

Slide 16 text

Creating

Slide 17

Slide 17 text

About

Slide 18

Slide 18 text

cycle amplitude Higher

Slide 19

Slide 19 text

Make

Slide 20

Slide 20 text

The

Slide 21

Slide 21 text

1

Slide 22

Slide 22 text

The

Slide 23

Slide 23 text

440Hz 880Hz 1760Hz 3520Hz A4 A5 A6 A7 A A A A Monday, 24 June, 13

Slide 24

Slide 24 text

difference

Slide 25

Slide 25 text

•Get

Slide 26

Slide 26 text

•To

Slide 27

Slide 27 text

Get

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

[[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

Slide 30

Slide 30 text

[[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

Slide 31

Slide 31 text

Harmonics •Sine

Slide 32

Slide 32 text

y=sin(x) y

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Envelopes •Changing

Slide 36

Slide 36 text

input

Slide 37

Slide 37 text

input

Slide 38

Slide 38 text

input

Slide 39

Slide 39 text

Digital

Slide 40

Slide 40 text

The

Slide 41

Slide 41 text

Monday, 24 June, 13

Slide 42

Slide 42 text

Monday, 24 June, 13

Slide 43

Slide 43 text

Create

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Putting

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Domain

Slide 50

Slide 50 text

Techniques

Slide 51

Slide 51 text

DSL

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Turk!h March Monday, 24 June, 13

Slide 55

Slide 55 text

Wait,

Slide 56

Slide 56 text

Otto the Algorithmic Composer Monday, 24 June, 13

Slide 57

Slide 57 text

Algorithmic composition is the technique of using algorithms to create music - Wikipedia Monday, 24 June, 13

Slide 58

Slide 58 text

Grab tweet from Twitter, create music from it Monday, 24 June, 13

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Grab tweet from Twitter, create music from it Scan a picture Get today’s weather report Monday, 24 June, 13

Slide 61

Slide 61 text

The algorithm Monday, 24 June, 13

Slide 62

Slide 62 text

Melody + Chords = Music Monday, 24 June, 13

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Convert each word into a number using the touch tone keypad algorithm Monday, 24 June, 13

Slide 66

Slide 66 text

Monday, 24 June, 13

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Melody + Chords = Music ✓ Monday, 24 June, 13

Slide 72

Slide 72 text

Start with the first note of the scale Monday, 24 June, 13

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Melody + Chords = Music ✓ ✓ Monday, 24 June, 13

Slide 78

Slide 78 text

Break down into 4 words per bar, use Muse to write to WAV file Monday, 24 June, 13

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

Demo Monday, 24 June, 13

Slide 81

Slide 81 text

@sausheong [email protected] Thank you for listening! Monday, 24 June, 13