This document discusses the domain-specific language Muse, created by Chang Sau Sheong. Muse is a pure Ruby language for synthesizing music. It allows users to create sounds, organize them into musical notes, write notes to files, and wrap the whole process in a domain-specific language. The document also provides background information on sound waves and how basic sounds are produced by varying amplitude and frequency.
351. 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
407. 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
408. 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
423. sounds
def envelope(input, duration)
Math.cos((Math::PI*input)/(2*duration.to_f))
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 * Math.sin(2 * Math::PI * frequency * i) *
envelope(i, duration)).to_i
stream [x,x]
end
543. 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
584. methods
def self.record(name, options ={}, block)
...
def bar(id, options={})
...
@bars[id] Bar.new(id, options)
@bars[id].last
end
end
class Bar
def notes(block)
...
end
end
end
require muse
include Muse
Song.record 'hotel_california', harmonic: 'guitar', bpm: 100 do
bar(1).notes { d4 b:2; e4; fis4;}
...
end
585. 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
586. 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
604. 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)
605. 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]
606. 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]}]
609. Calculate the ‘distance’
between the current note and
the next
def distance(a_word)
word_to_num(a_word).to_i % 5
end
610. 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
611. 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
612. 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