muse 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/muse/config/adsr.rb +9 -0
- data/lib/muse/config/chords.rb +30 -0
- data/lib/muse/config.rb +1 -0
- data/lib/muse/version.rb +3 -0
- data/lib/muse/wav.rb +79 -0
- data/lib/muse.rb +149 -0
- metadata +66 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Muse
|
2
|
+
module Config
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# major
|
6
|
+
def _CM(options={})
|
7
|
+
add_to_stream chord(%w(c2 e2 g2), options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def _FM(options={})
|
11
|
+
add_to_stream chord(%w(f2 a2 c3), options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# minor
|
15
|
+
def _Dm(options={})
|
16
|
+
add_to_stream chord(%w(d2 f2 a2), options)
|
17
|
+
end
|
18
|
+
|
19
|
+
# dominant 7th
|
20
|
+
def _C7(options={})
|
21
|
+
add_to_stream chord(%w(c2 e2 g2 ais2), options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _BbM(options={})
|
25
|
+
add_to_stream chord(%w(ais2 d3 f3), options)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/muse/config.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{File.dirname(__FILE__)}/config/*.rb"].each{|f| require f }
|
data/lib/muse/version.rb
ADDED
data/lib/muse/wav.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
module Muse
|
4
|
+
class RiffChunk < BinData::Record
|
5
|
+
int32be :chunk_id
|
6
|
+
int32le :chunk_size
|
7
|
+
int32be :format
|
8
|
+
end
|
9
|
+
|
10
|
+
class FormatChunk < BinData::Record
|
11
|
+
int32be :chunk_id
|
12
|
+
int32le :chunk_size
|
13
|
+
int16le :audio_format
|
14
|
+
int16le :num_channels
|
15
|
+
int32le :sample_rate
|
16
|
+
int32le :byte_rate
|
17
|
+
int16le :block_align
|
18
|
+
int16le :bits_per_sample
|
19
|
+
end
|
20
|
+
|
21
|
+
class DataChunk < BinData::Record
|
22
|
+
int32be :chunk_id
|
23
|
+
int32le :chunk_size
|
24
|
+
array :stream do
|
25
|
+
int16le :left
|
26
|
+
int16le :right
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class WavFormat < BinData::Record
|
31
|
+
riff_chunk :riff_chunk
|
32
|
+
format_chunk :format_chunk
|
33
|
+
data_chunk :data_chunk
|
34
|
+
end
|
35
|
+
|
36
|
+
class Wav
|
37
|
+
SAMPLE_RATE = 44100
|
38
|
+
attr :wav, :file, :sample_rate, :format_chunk, :riff_chunk, :data_chunk
|
39
|
+
|
40
|
+
def initialize(filename)
|
41
|
+
@sample_rate = SAMPLE_RATE
|
42
|
+
@file = File.open(filename, "wb")
|
43
|
+
|
44
|
+
@riff_chunk = RiffChunk.new
|
45
|
+
@riff_chunk.chunk_id = "RIFF".unpack("N").first
|
46
|
+
@riff_chunk.format = "WAVE".unpack("N").first
|
47
|
+
|
48
|
+
@format_chunk = FormatChunk.new
|
49
|
+
@format_chunk.chunk_id = "fmt ".unpack("N").first
|
50
|
+
@format_chunk.chunk_size = 16
|
51
|
+
@format_chunk.audio_format = 1
|
52
|
+
@format_chunk.num_channels = 2
|
53
|
+
@format_chunk.bits_per_sample = 16
|
54
|
+
@format_chunk.sample_rate = @sample_rate
|
55
|
+
@format_chunk.byte_rate = @format_chunk.sample_rate * @format_chunk.num_channels * @format_chunk.bits_per_sample/2
|
56
|
+
@format_chunk.block_align = @format_chunk.num_channels * @format_chunk.bits_per_sample/2
|
57
|
+
@data_chunk = DataChunk.new
|
58
|
+
@data_chunk.chunk_id = "data".unpack("N").first
|
59
|
+
end
|
60
|
+
|
61
|
+
def write(stream_data)
|
62
|
+
stream_data.each_with_index do |s,i|
|
63
|
+
@data_chunk.stream[i].left = s[0]
|
64
|
+
@data_chunk.stream[i].right = s[1]
|
65
|
+
end
|
66
|
+
@data_chunk.chunk_size = stream_data.length * @format_chunk.num_channels * @format_chunk.bits_per_sample/8
|
67
|
+
@riff_chunk.chunk_size = 36 + @data_chunk.chunk_size
|
68
|
+
@wav = WavFormat.new
|
69
|
+
@wav.riff_chunk = @riff_chunk
|
70
|
+
@wav.format_chunk = @format_chunk
|
71
|
+
@wav.data_chunk = @data_chunk
|
72
|
+
@wav.write(@file)
|
73
|
+
end
|
74
|
+
|
75
|
+
def close
|
76
|
+
@file.close
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/muse.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require "muse/wav"
|
2
|
+
require "muse/config"
|
3
|
+
|
4
|
+
module Muse
|
5
|
+
class Song
|
6
|
+
attr :wav_file, :bars
|
7
|
+
|
8
|
+
def self.record(name, &block)
|
9
|
+
puts "Start recording song named #{name}.wav"
|
10
|
+
@wav_file = Wav.new(name + ".wav")
|
11
|
+
@bars = {}
|
12
|
+
puts "Processing ..."
|
13
|
+
instance_eval &block
|
14
|
+
save
|
15
|
+
puts "done."
|
16
|
+
end
|
17
|
+
|
18
|
+
class Bar
|
19
|
+
attr :bpm, :beats, :adsr
|
20
|
+
attr_accessor :stream
|
21
|
+
|
22
|
+
NOTES = %w(_ a ais b c cis d dis e f fis g gis)
|
23
|
+
FREQUENCIES = {
|
24
|
+
:a2 => -24, :ais2 => -23, :b2 => -22, :c3 => -21, :cis3 => -20,
|
25
|
+
:d3 => -19, :dis3 => -18, :e3 => -17, :f3 => -16, :fis3 => -15,
|
26
|
+
:g3 => -14, :gis3 => -13, :a3 => -12, :ais3 => -11, :b3 => -10,
|
27
|
+
:c4 => -9, :cis4 => -8, :d4 => -7, :dis4 => -6, :e4 => -5,
|
28
|
+
:f4 => -4, :fis4 => -3, :g4 => -2, :gis4 => -1, :a4 => 0,
|
29
|
+
:ais4 => 1, :b4 => 2, :c5 => 3, :cis5 => 4, :d5 => 5,
|
30
|
+
:dis5 => 6, :e5 => 7, :f5 => 8, :fis5 => 9, :g5 => 10,
|
31
|
+
:gis5 => 11, :a5 => 12, :ais5 => 13, :b5 => 14, :c6 => 15,
|
32
|
+
:cis6 => 16, :d6 => 17, :dis6 => 18, :e6 => 19, :f6 => 20,
|
33
|
+
:fis6 => 21, :g6 => 22, :gis6 => 23
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(id, options={})
|
37
|
+
@bpm = options[:bpm] || 120
|
38
|
+
@beats = (options[:b] || 1).to_f
|
39
|
+
@adsr = options[:adsr] || 'default'
|
40
|
+
@stream = []
|
41
|
+
end
|
42
|
+
|
43
|
+
def notes(&block)
|
44
|
+
instance_eval &block
|
45
|
+
end
|
46
|
+
|
47
|
+
def frequency_of(step)
|
48
|
+
440.0*(2**(step.to_f/12.0))
|
49
|
+
end
|
50
|
+
|
51
|
+
def chord(notes,options={})
|
52
|
+
puts "chord with #{notes}"
|
53
|
+
triad =[]
|
54
|
+
notes.each do |name|
|
55
|
+
if name.start_with? *NOTES
|
56
|
+
octave = name[name.length-1].to_i
|
57
|
+
note = octave > 0 ? name.chop : name
|
58
|
+
octave = 3 if octave == 0
|
59
|
+
triad << note_data(note, octave, options)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
triad.transpose.map {|x| x.transpose.map {|y| y.reduce(:+)}}
|
63
|
+
end
|
64
|
+
|
65
|
+
def note_data(note, octave=3, options={})
|
66
|
+
stream = []
|
67
|
+
if options
|
68
|
+
beats = options[:b].nil? ? (@beats || 1) : options[:b].to_f
|
69
|
+
volume = (options[:v].nil? ? 10 : options[:v].to_i) * 1000
|
70
|
+
adsr = options[:a].nil? ? @adsr : 'default'
|
71
|
+
else
|
72
|
+
beats, volume, adsr = (@beats || 1), 10000, 'default'
|
73
|
+
end
|
74
|
+
puts "[#{note}] -> beats : #{beats}, :octave : #{octave}"
|
75
|
+
duration = ((60 * Wav::SAMPLE_RATE * beats)/@bpm)/Wav::SAMPLE_RATE.to_f
|
76
|
+
note_frequency = note + octave.to_s
|
77
|
+
unless note == '_'
|
78
|
+
freq = frequency_of(FREQUENCIES[note_frequency.to_sym])
|
79
|
+
else
|
80
|
+
freq = 0
|
81
|
+
end
|
82
|
+
(0.0..duration.to_f).step(1.0/Wav::SAMPLE_RATE) do |i|
|
83
|
+
x = (Config.send(adsr.to_sym,i) * volume * Math.sin(2 * Math::PI * freq * i)).to_i
|
84
|
+
stream << [x,x]
|
85
|
+
end
|
86
|
+
return stream
|
87
|
+
end
|
88
|
+
|
89
|
+
def truncate_stream_by(num)
|
90
|
+
num.times {@stream.pop}
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_to_stream(str)
|
94
|
+
@stream += str
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing(name, *args, &block)
|
98
|
+
name = name.to_s
|
99
|
+
if name.start_with? *NOTES
|
100
|
+
if name.split('_').length > 1
|
101
|
+
notes = name.split('_')
|
102
|
+
add_to_stream chord(notes, args[0])
|
103
|
+
else
|
104
|
+
octave = name[name.length-1].to_i
|
105
|
+
note = octave > 0 ? name.chop : name
|
106
|
+
octave = 3 if octave == 0
|
107
|
+
add_to_stream note_data(note, octave, args[0])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
class << self
|
115
|
+
def bar(id, options={})
|
116
|
+
puts "bar #{id}"
|
117
|
+
unless @bars[id]
|
118
|
+
@bars[id] = []
|
119
|
+
end
|
120
|
+
@bars[id] << Bar.new(id, options)
|
121
|
+
@bars[id].last
|
122
|
+
end
|
123
|
+
|
124
|
+
def right_size(bars)
|
125
|
+
container = []
|
126
|
+
min_bar = bars.min_by {|x| x.stream.length}
|
127
|
+
bars.map do |bar|
|
128
|
+
bar.truncate_stream_by(bar.stream.length - min_bar.stream.length)
|
129
|
+
bar
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def save
|
134
|
+
puts "Saving music file"
|
135
|
+
stream = []
|
136
|
+
@bars.each do |id, item|
|
137
|
+
container = []
|
138
|
+
item = right_size item
|
139
|
+
item.each do |i|
|
140
|
+
container << i.stream
|
141
|
+
end
|
142
|
+
stream += container.transpose.map {|x| x.transpose.map {|y| y.reduce(:+)}}
|
143
|
+
end
|
144
|
+
@wav_file.write(stream)
|
145
|
+
@wav_file.close
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: muse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chang Sau Sheong
|
9
|
+
- Ng Tze Yang
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-03-11 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bindata
|
17
|
+
requirement: &2168647260 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2168647260
|
26
|
+
description: Muse is a complete music creation package including writing and up to
|
27
|
+
creating WAV files
|
28
|
+
email:
|
29
|
+
- sausheong@gmail.com
|
30
|
+
- ngty77@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- lib/muse/config/adsr.rb
|
36
|
+
- lib/muse/config/chords.rb
|
37
|
+
- lib/muse/config.rb
|
38
|
+
- lib/muse/version.rb
|
39
|
+
- lib/muse/wav.rb
|
40
|
+
- lib/muse.rb
|
41
|
+
homepage: https://github.com/sausheong/muse
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.17
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Muse is a Ruby DSL for making music
|
66
|
+
test_files: []
|