beats 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +14 -9
- data/bin/beats +4 -8
- data/lib/kit.rb +27 -13
- data/lib/pattern.rb +15 -4
- data/lib/song.rb +10 -110
- data/lib/songparser.rb +169 -0
- data/test/includes.rb +1 -0
- data/test/kit_test.rb +22 -17
- data/test/pattern_test.rb +5 -5
- data/test/song_test.rb +13 -99
- data/test/songparser_test.rb +207 -0
- metadata +5 -2
data/README.markdown
CHANGED
@@ -10,18 +10,23 @@ BEATS is a drum machine written in pure Ruby. Feed it a song notated in YAML, an
|
|
10
10
|
- Chorus: x4
|
11
11
|
- Verse: x2
|
12
12
|
- Chorus: x4
|
13
|
+
Kit:
|
14
|
+
- bass: sounds/bass.wav
|
15
|
+
- snare: sounds/snare.wav
|
16
|
+
- hh_closed: sounds/hh_closed.wav
|
17
|
+
- agogo: sounds/agogo_high.wav
|
13
18
|
|
14
19
|
Verse:
|
15
|
-
- bass
|
16
|
-
- snare
|
17
|
-
- hh_closed
|
18
|
-
-
|
20
|
+
- bass: X...X...X...X...
|
21
|
+
- snare: ..............X.
|
22
|
+
- hh_closed: X.XXX.XXX.X.X.X.
|
23
|
+
- agogo: ..............XX
|
19
24
|
|
20
25
|
Chorus:
|
21
|
-
- bass
|
22
|
-
- snare
|
23
|
-
- hh_closed
|
24
|
-
|
25
|
-
|
26
|
+
- bass: X...X...X...X...
|
27
|
+
- snare: ....X.......X...
|
28
|
+
- hh_closed: X.XXX.XXX.XX..X.
|
29
|
+
- sounds/tom4.wav: ...........X....
|
30
|
+
- sounds/tom2.wav: ..............X.
|
26
31
|
|
27
32
|
For installation and usage instructions, visit the BEATS website at [http://beatsdrummachine.com](http://beatsdrummachine.com).
|
data/bin/beats
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require File.dirname(__FILE__) + "/../lib/song"
|
4
|
+
require File.dirname(__FILE__) + "/../lib/songparser"
|
4
5
|
require File.dirname(__FILE__) + "/../lib/kit"
|
5
6
|
require File.dirname(__FILE__) + "/../lib/pattern"
|
6
7
|
require File.dirname(__FILE__) + "/../lib/track"
|
@@ -9,7 +10,7 @@ require "optparse"
|
|
9
10
|
require "yaml"
|
10
11
|
require "wavefile"
|
11
12
|
|
12
|
-
BEATS_VERSION = "1.
|
13
|
+
BEATS_VERSION = "1.1.0"
|
13
14
|
SAMPLE_RATE = 44100
|
14
15
|
|
15
16
|
def parse_options
|
@@ -63,7 +64,8 @@ end
|
|
63
64
|
|
64
65
|
begin
|
65
66
|
parse_start_time = Time.now
|
66
|
-
|
67
|
+
song_parser = SongParser.new()
|
68
|
+
song_from_file = song_parser.parse(File.dirname(input_file), YAML.load_file(input_file))
|
67
69
|
|
68
70
|
generate_samples_start = Time.now
|
69
71
|
sample_data = song_from_file.sample_data(options[:pattern], options[:split])
|
@@ -90,12 +92,6 @@ rescue Errno::ENOENT => detail
|
|
90
92
|
puts ""
|
91
93
|
puts "Song file '#{input_file}' not found."
|
92
94
|
puts ""
|
93
|
-
rescue ArgumentError => detail
|
94
|
-
puts ""
|
95
|
-
puts "Song file '#{input_file}' has an error:"
|
96
|
-
puts " Syntax error in YAML file:"
|
97
|
-
puts " #{detail}"
|
98
|
-
puts ""
|
99
95
|
rescue SongParseError => detail
|
100
96
|
puts ""
|
101
97
|
puts "Song file '#{input_file}' has an error:"
|
data/lib/kit.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
class SoundNotFoundError < RuntimeError; end
|
2
|
+
|
1
3
|
class Kit
|
2
|
-
|
4
|
+
PATH_SEPARATOR = File.const_get("SEPARATOR")
|
5
|
+
|
6
|
+
def initialize(base_path)
|
7
|
+
@base_path = base_path
|
3
8
|
@sounds = {}
|
4
9
|
@num_channels = 0
|
5
10
|
@bits_per_sample = 0
|
@@ -7,28 +12,37 @@ class Kit
|
|
7
12
|
|
8
13
|
def add(name, path)
|
9
14
|
if(!@sounds.has_key? name)
|
10
|
-
|
11
|
-
|
15
|
+
if(!path.start_with?(PATH_SEPARATOR))
|
16
|
+
path = @base_path + PATH_SEPARATOR + path
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
wavefile = WaveFile.open(path)
|
21
|
+
rescue
|
22
|
+
raise SoundNotFoundError, "Sound file #{name} not found."
|
23
|
+
end
|
24
|
+
|
25
|
+
@sounds[name] = wavefile
|
12
26
|
|
13
|
-
if
|
14
|
-
@num_channels =
|
27
|
+
if wavefile.num_channels > @num_channels
|
28
|
+
@num_channels = wavefile.num_channels
|
15
29
|
end
|
16
|
-
if
|
17
|
-
@bits_per_sample =
|
30
|
+
if wavefile.bits_per_sample > @bits_per_sample
|
31
|
+
@bits_per_sample = wavefile.bits_per_sample
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
21
35
|
|
22
36
|
def get_sample_data(name)
|
23
|
-
|
37
|
+
wavefile = @sounds[name]
|
24
38
|
|
25
|
-
if
|
39
|
+
if wavefile == nil
|
26
40
|
raise StandardError, "Kit doesn't contain sound '#{name}'."
|
27
41
|
else
|
28
|
-
|
29
|
-
|
42
|
+
wavefile.num_channels = @num_channels
|
43
|
+
wavefile.bits_per_sample = @bits_per_sample
|
30
44
|
|
31
|
-
return
|
45
|
+
return wavefile.sample_data
|
32
46
|
end
|
33
47
|
end
|
34
48
|
|
@@ -36,5 +50,5 @@ class Kit
|
|
36
50
|
return @sounds.length
|
37
51
|
end
|
38
52
|
|
39
|
-
attr_reader :bits_per_sample, :num_channels
|
53
|
+
attr_reader :base_path, :bits_per_sample, :num_channels
|
40
54
|
end
|
data/lib/pattern.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
class Pattern
|
2
2
|
def initialize(name)
|
3
3
|
@name = name
|
4
|
+
@cache = {}
|
4
5
|
@tracks = {}
|
5
6
|
end
|
6
7
|
|
@@ -47,6 +48,12 @@ class Pattern
|
|
47
48
|
private
|
48
49
|
|
49
50
|
def combined_sample_data(tick_sample_length, num_channels, num_tracks_in_song, incoming_overflow)
|
51
|
+
# If we've already encountered this pattern with the same incoming overflow before,
|
52
|
+
# return the pre-mixed down version from the cache.
|
53
|
+
if(@cache.member?(incoming_overflow))
|
54
|
+
return @cache[incoming_overflow]
|
55
|
+
end
|
56
|
+
|
50
57
|
fill_value = (num_channels == 1) ? 0 : [].fill(0, 0, num_channels)
|
51
58
|
track_names = @tracks.keys
|
52
59
|
primary_sample_data = []
|
@@ -64,8 +71,8 @@ private
|
|
64
71
|
(0...track_samples.length).each {|i| primary_sample_data[i] += track_samples[i] }
|
65
72
|
else
|
66
73
|
(0...track_samples.length).each {|i|
|
67
|
-
primary_sample_data[i]
|
68
|
-
|
74
|
+
primary_sample_data[i][0] += track_samples[i][0]
|
75
|
+
primary_sample_data[i][1] += track_samples[i][1]
|
69
76
|
}
|
70
77
|
end
|
71
78
|
|
@@ -86,14 +93,18 @@ private
|
|
86
93
|
end
|
87
94
|
}
|
88
95
|
|
89
|
-
# Mix down the tracks into one
|
96
|
+
# Mix down the pattern's tracks into one single track
|
90
97
|
if(num_channels == 1)
|
91
98
|
primary_sample_data = primary_sample_data.map {|sample| (sample / num_tracks_in_song).round }
|
92
99
|
else
|
93
100
|
primary_sample_data = primary_sample_data.map {|sample| [(sample[0] / num_tracks_in_song).round, (sample[1] / num_tracks_in_song).round] }
|
94
101
|
end
|
95
102
|
|
96
|
-
|
103
|
+
# Add the result to the cache so we don't have to go through all of this the next time...
|
104
|
+
mixdown_sample_data = {:primary => primary_sample_data, :overflow => overflow_sample_data}
|
105
|
+
@cache[incoming_overflow] = mixdown_sample_data
|
106
|
+
|
107
|
+
return mixdown_sample_data
|
97
108
|
end
|
98
109
|
|
99
110
|
def split_sample_data(tick_sample_length, num_channels, incoming_overflow)
|
data/lib/song.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
|
-
class
|
1
|
+
class InvalidTempoError < RuntimeError; end
|
2
2
|
|
3
3
|
class Song
|
4
4
|
SAMPLE_RATE = 44100
|
5
5
|
SECONDS_PER_MINUTE = 60.0
|
6
|
-
|
6
|
+
SAMPLES_PER_MINUTE = SAMPLE_RATE * SECONDS_PER_MINUTE
|
7
|
+
DEFAULT_TEMPO = 120
|
7
8
|
|
8
|
-
def initialize(
|
9
|
-
self.tempo =
|
10
|
-
@
|
11
|
-
@kit = Kit.new()
|
9
|
+
def initialize(base_path)
|
10
|
+
self.tempo = DEFAULT_TEMPO
|
11
|
+
@kit = Kit.new(base_path)
|
12
12
|
@patterns = {}
|
13
13
|
@structure = []
|
14
|
-
|
15
|
-
if(definition != nil)
|
16
|
-
parse(definition)
|
17
|
-
end
|
18
14
|
end
|
19
15
|
|
20
16
|
def pattern(name)
|
@@ -86,15 +82,15 @@ class Song
|
|
86
82
|
|
87
83
|
def tempo=(new_tempo)
|
88
84
|
if(new_tempo.class != Fixnum || new_tempo <= 0)
|
89
|
-
raise
|
85
|
+
raise InvalidTempoError, "Invalid tempo: '#{new_tempo}'. Tempo must be a number greater than 0."
|
90
86
|
end
|
91
87
|
|
92
88
|
@tempo = new_tempo
|
93
|
-
@tick_sample_length =
|
89
|
+
@tick_sample_length = SAMPLES_PER_MINUTE / new_tempo / 4.0
|
94
90
|
end
|
95
91
|
|
96
|
-
attr_reader :
|
97
|
-
attr_accessor :structure
|
92
|
+
attr_reader :tick_sample_length, :patterns
|
93
|
+
attr_accessor :structure, :kit
|
98
94
|
|
99
95
|
private
|
100
96
|
|
@@ -117,102 +113,6 @@ private
|
|
117
113
|
|
118
114
|
return merged_sample_data
|
119
115
|
end
|
120
|
-
|
121
|
-
# Converts all hash keys to be lowercase
|
122
|
-
def downcase_hash_keys(hash)
|
123
|
-
return hash.inject({}) {|new_hash, pair|
|
124
|
-
new_hash[pair.first.downcase] = pair.last
|
125
|
-
new_hash
|
126
|
-
}
|
127
|
-
end
|
128
|
-
|
129
|
-
def parse(definition)
|
130
|
-
if(definition.class == String)
|
131
|
-
song_definition = YAML.load(definition)
|
132
|
-
elsif(definition.class == Hash)
|
133
|
-
song_definition = definition
|
134
|
-
else
|
135
|
-
raise StandardError, "Invalid song input"
|
136
|
-
end
|
137
|
-
|
138
|
-
@kit = build_kit(song_definition)
|
139
|
-
|
140
|
-
song_definition = downcase_hash_keys(song_definition)
|
141
|
-
|
142
|
-
# Process each pattern
|
143
|
-
song_definition.keys.each{|key|
|
144
|
-
if(key != "song")
|
145
|
-
new_pattern = self.pattern key.to_sym
|
146
|
-
|
147
|
-
track_list = song_definition[key]
|
148
|
-
track_list.each{|track_definition|
|
149
|
-
track_name = track_definition.keys.first
|
150
|
-
new_pattern.track track_name, @kit.get_sample_data(track_name), track_definition[track_name]
|
151
|
-
}
|
152
|
-
end
|
153
|
-
}
|
154
|
-
|
155
|
-
# Process song header
|
156
|
-
parse_song_header(downcase_hash_keys(song_definition["song"]))
|
157
|
-
end
|
158
|
-
|
159
|
-
def parse_song_header(header_data)
|
160
|
-
self.tempo = header_data["tempo"]
|
161
|
-
|
162
|
-
pattern_list = header_data["structure"]
|
163
|
-
structure = []
|
164
|
-
pattern_list.each{|pattern_item|
|
165
|
-
if(pattern_item.class == String)
|
166
|
-
pattern_item = {pattern_item => "x1"}
|
167
|
-
end
|
168
|
-
|
169
|
-
pattern_name = pattern_item.keys.first
|
170
|
-
pattern_name_sym = pattern_name.downcase.to_sym
|
171
|
-
|
172
|
-
if(!@patterns.has_key?(pattern_name_sym))
|
173
|
-
raise SongParseError, "Song structure includes non-existant pattern: #{pattern_name}."
|
174
|
-
end
|
175
|
-
|
176
|
-
multiples_str = pattern_item[pattern_name]
|
177
|
-
multiples_str.slice!(0)
|
178
|
-
multiples = multiples_str.to_i
|
179
|
-
|
180
|
-
if(multiples_str.match(/[^0-9]/) != nil)
|
181
|
-
raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Number of repeats should be a whole number."
|
182
|
-
elsif(multiples < 0)
|
183
|
-
raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Must be 0 or greater."
|
184
|
-
end
|
185
|
-
|
186
|
-
multiples.times { structure << pattern_name_sym }
|
187
|
-
}
|
188
|
-
|
189
|
-
@structure = structure
|
190
|
-
end
|
191
|
-
|
192
|
-
def build_kit(song_definition)
|
193
|
-
kit = Kit.new()
|
194
|
-
|
195
|
-
song_definition.keys.each{|key|
|
196
|
-
if(key.downcase != "song")
|
197
|
-
track_list = song_definition[key]
|
198
|
-
track_list.each{|track_definition|
|
199
|
-
track_name = track_definition.keys.first
|
200
|
-
track_path = track_name
|
201
|
-
if(!track_path.start_with?(PATH_SEPARATOR))
|
202
|
-
track_path = @input_path + PATH_SEPARATOR + track_path
|
203
|
-
end
|
204
|
-
|
205
|
-
if(!File.exists? track_path)
|
206
|
-
raise SongParseError, "File '#{track_name}' not found for pattern '#{key}'"
|
207
|
-
end
|
208
|
-
|
209
|
-
kit.add(track_name, track_path)
|
210
|
-
}
|
211
|
-
end
|
212
|
-
}
|
213
|
-
|
214
|
-
return kit
|
215
|
-
end
|
216
116
|
|
217
117
|
def sample_data_split_all_patterns(fill_value, num_tracks_in_song)
|
218
118
|
output_data = {}
|
data/lib/songparser.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
class SongParseError < RuntimeError; end
|
2
|
+
|
3
|
+
class SongParser
|
4
|
+
NO_SONG_HEADER_ERROR_MSG =
|
5
|
+
"Song must have a header. Here's an example:
|
6
|
+
|
7
|
+
Song:
|
8
|
+
Tempo: 120
|
9
|
+
Structure:
|
10
|
+
- Verse: x2
|
11
|
+
- Chorus: x2"
|
12
|
+
|
13
|
+
def initialize()
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(base_path, definition = nil)
|
17
|
+
raw_song_definition = canonicalize_definition(definition)
|
18
|
+
raw_song_components = split_raw_yaml_into_components(raw_song_definition)
|
19
|
+
|
20
|
+
song = Song.new(base_path)
|
21
|
+
|
22
|
+
# 1.) Set tempo
|
23
|
+
begin
|
24
|
+
if raw_song_components[:tempo] != nil
|
25
|
+
song.tempo = raw_song_components[:tempo]
|
26
|
+
end
|
27
|
+
rescue InvalidTempoError => detail
|
28
|
+
raise SongParseError, "#{detail}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# 2.) Build the kit
|
32
|
+
begin
|
33
|
+
kit = build_kit(base_path, raw_song_components[:kit], raw_song_components[:patterns])
|
34
|
+
rescue SoundNotFoundError => detail
|
35
|
+
raise SongParseError, "#{detail}"
|
36
|
+
end
|
37
|
+
song.kit = kit
|
38
|
+
|
39
|
+
# 3.) Load patterns
|
40
|
+
add_patterns_to_song(song, raw_song_components[:patterns])
|
41
|
+
|
42
|
+
# 4.) Set structure
|
43
|
+
if(raw_song_components[:structure] == nil)
|
44
|
+
raise SongParseError, "Song must have a Structure section in the header."
|
45
|
+
else
|
46
|
+
set_song_structure(song, raw_song_components[:structure])
|
47
|
+
end
|
48
|
+
|
49
|
+
return song
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# This is basically a factory. Don't see a benefit to extracting to a full class.
|
55
|
+
# Also, is "canonicalize" a word?
|
56
|
+
def canonicalize_definition(definition)
|
57
|
+
if(definition.class == String)
|
58
|
+
begin
|
59
|
+
raw_song_definition = YAML.load(definition)
|
60
|
+
rescue ArgumentError => detail
|
61
|
+
raise SongParseError, "Syntax error in YAML file"
|
62
|
+
end
|
63
|
+
elsif(definition.class == Hash)
|
64
|
+
raw_song_definition = definition
|
65
|
+
else
|
66
|
+
raise SongParseError, "Invalid song input"
|
67
|
+
end
|
68
|
+
|
69
|
+
return raw_song_definition
|
70
|
+
end
|
71
|
+
|
72
|
+
def split_raw_yaml_into_components(raw_song_definition)
|
73
|
+
raw_song_components = {}
|
74
|
+
raw_song_components[:full_definition] = downcase_hash_keys(raw_song_definition)
|
75
|
+
|
76
|
+
if(raw_song_components[:full_definition]["song"] != nil)
|
77
|
+
raw_song_components[:header] = downcase_hash_keys(raw_song_components[:full_definition]["song"])
|
78
|
+
else
|
79
|
+
raise SongParseError, NO_SONG_HEADER_ERROR_MSG
|
80
|
+
end
|
81
|
+
raw_song_components[:tempo] = raw_song_components[:header]["tempo"]
|
82
|
+
raw_song_components[:kit] = raw_song_components[:header]["kit"]
|
83
|
+
raw_song_components[:structure] = raw_song_components[:header]["structure"]
|
84
|
+
raw_song_components[:patterns] = raw_song_components[:full_definition].reject {|k, v| k == "song"}
|
85
|
+
|
86
|
+
return raw_song_components
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_kit(base_path, raw_kit, raw_patterns)
|
90
|
+
kit = Kit.new(base_path)
|
91
|
+
|
92
|
+
# Add sounds defined in the Kit section of the song header
|
93
|
+
if(raw_kit != nil)
|
94
|
+
raw_kit.each {|kit_item|
|
95
|
+
kit.add(kit_item.keys.first, kit_item.values.first)
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add sounds not defined in Kit section, but used in individual tracks
|
100
|
+
# TODO Investigate detecting duplicate keys already defined in the Kit section, as this could possibly
|
101
|
+
# result in a performance improvement when the sound has to be converted to a different bit rate/num channels,
|
102
|
+
# as well as use less memory.
|
103
|
+
raw_patterns.keys.each{|key|
|
104
|
+
track_list = raw_patterns[key]
|
105
|
+
track_list.each{|track_definition|
|
106
|
+
track_name = track_definition.keys.first
|
107
|
+
track_path = track_name
|
108
|
+
|
109
|
+
kit.add(track_name, track_path)
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
return kit
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_patterns_to_song(song, raw_patterns)
|
117
|
+
raw_patterns.keys.each{|key|
|
118
|
+
new_pattern = song.pattern key.to_sym
|
119
|
+
|
120
|
+
track_list = raw_patterns[key]
|
121
|
+
track_list.each{|track_definition|
|
122
|
+
track_name = track_definition.keys.first
|
123
|
+
new_pattern.track track_name, song.kit.get_sample_data(track_name), track_definition[track_name]
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_song_structure(song, raw_structure)
|
129
|
+
structure = []
|
130
|
+
|
131
|
+
raw_structure.each{|pattern_item|
|
132
|
+
if(pattern_item.class == String)
|
133
|
+
pattern_item = {pattern_item => "x1"}
|
134
|
+
end
|
135
|
+
|
136
|
+
pattern_name = pattern_item.keys.first
|
137
|
+
pattern_name_sym = pattern_name.downcase.to_sym
|
138
|
+
|
139
|
+
# Convert the number of repeats from a String such as "x4" into an integer such as 4.
|
140
|
+
multiples_str = pattern_item[pattern_name]
|
141
|
+
multiples_str.slice!(0)
|
142
|
+
multiples = multiples_str.to_i
|
143
|
+
|
144
|
+
if(multiples_str.match(/[^0-9]/) != nil)
|
145
|
+
raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Number of repeats should be a whole number."
|
146
|
+
else
|
147
|
+
if(multiples < 0)
|
148
|
+
raise SongParseError, "'#{multiples_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Must be 0 or greater."
|
149
|
+
elsif(multiples > 0 && !song.patterns.has_key?(pattern_name_sym))
|
150
|
+
# This test is purposefully designed to only throw an error if the number of repeats is greater
|
151
|
+
# than 0. This allows you to specify an undefined pattern in the structure with "x0" repeats.
|
152
|
+
# This can be convenient for defining the structure before all patterns have been added to the song file.
|
153
|
+
raise SongParseError, "Song structure includes non-existent pattern: #{pattern_name}."
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
multiples.times { structure << pattern_name_sym }
|
158
|
+
}
|
159
|
+
song.structure = structure
|
160
|
+
end
|
161
|
+
|
162
|
+
# Converts all hash keys to be lowercase
|
163
|
+
def downcase_hash_keys(hash)
|
164
|
+
return hash.inject({}) {|new_hash, pair|
|
165
|
+
new_hash[pair.first.downcase] = pair.last
|
166
|
+
new_hash
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
data/test/includes.rb
CHANGED
data/test/kit_test.rb
CHANGED
@@ -6,50 +6,55 @@ class SongTest < Test::Unit::TestCase
|
|
6
6
|
MIN_SAMPLE_8BIT = 0
|
7
7
|
MAX_SAMPLE_8BIT = 255
|
8
8
|
|
9
|
-
def
|
9
|
+
def test_valid_add
|
10
10
|
# Test adding sounds with progressively higher bits per sample and num channels.
|
11
11
|
# Verify that kit.bits_per_sample and kit.num_channels is ratcheted up.
|
12
|
-
kit = Kit.new()
|
12
|
+
kit = Kit.new("test/sounds")
|
13
13
|
assert_equal(kit.bits_per_sample, 0)
|
14
14
|
assert_equal(kit.num_channels, 0)
|
15
15
|
assert_equal(kit.size, 0)
|
16
|
-
kit.add("mono8", "
|
16
|
+
kit.add("mono8", "bass_mono_8.wav")
|
17
17
|
assert_equal(kit.bits_per_sample, 8)
|
18
18
|
assert_equal(kit.num_channels, 1)
|
19
19
|
assert_equal(kit.size, 1)
|
20
|
-
kit.add("mono16", "
|
20
|
+
kit.add("mono16", "bass_mono_16.wav")
|
21
21
|
assert_equal(kit.bits_per_sample, 16)
|
22
22
|
assert_equal(kit.num_channels, 1)
|
23
23
|
assert_equal(kit.size, 2)
|
24
|
-
kit.add("stereo16", "
|
24
|
+
kit.add("stereo16", "bass_stereo_16.wav")
|
25
25
|
assert_equal(kit.bits_per_sample, 16)
|
26
26
|
assert_equal(kit.num_channels, 2)
|
27
27
|
assert_equal(kit.size, 3)
|
28
28
|
|
29
29
|
# Test adding sounds with progressively lower bits per sample and num channels.
|
30
30
|
# Verify that kit.bits_per_sample and kit.num_channels doesn't change.
|
31
|
-
kit = Kit.new()
|
31
|
+
kit = Kit.new("test/sounds")
|
32
32
|
assert_equal(kit.bits_per_sample, 0)
|
33
33
|
assert_equal(kit.num_channels, 0)
|
34
|
-
kit.add("stereo16", "
|
34
|
+
kit.add("stereo16", "bass_stereo_16.wav")
|
35
35
|
assert_equal(kit.bits_per_sample, 16)
|
36
36
|
assert_equal(kit.num_channels, 2)
|
37
|
-
kit.add("mono16", "
|
37
|
+
kit.add("mono16", "bass_mono_16.wav")
|
38
38
|
assert_equal(kit.bits_per_sample, 16)
|
39
39
|
assert_equal(kit.num_channels, 2)
|
40
|
-
kit.add("mono8", "
|
40
|
+
kit.add("mono8", "bass_mono_8.wav")
|
41
41
|
assert_equal(kit.bits_per_sample, 16)
|
42
42
|
assert_equal(kit.num_channels, 2)
|
43
43
|
end
|
44
44
|
|
45
|
+
def test_invalid_add
|
46
|
+
kit = Kit.new("test/sounds")
|
47
|
+
assert_raise(SoundNotFoundError) { kit.add("i_do_not_exist", "i_do_not_exist.wav") }
|
48
|
+
end
|
49
|
+
|
45
50
|
def test_get_sample_data
|
46
|
-
kit = Kit.new()
|
51
|
+
kit = Kit.new("test/sounds")
|
47
52
|
|
48
53
|
assert_raise(StandardError) { kit.get_sample_data("nonexistant") }
|
49
54
|
|
50
55
|
# Test adding sounds with progressively higher bits per sample and num channels.
|
51
56
|
# Verify that sample data bits per sample and num channels is ratcheted up.
|
52
|
-
kit.add("mono8", "
|
57
|
+
kit.add("mono8", "bass_mono_8.wav")
|
53
58
|
sample_data = kit.get_sample_data("mono8")
|
54
59
|
assert(sample_data.max <= MAX_SAMPLE_8BIT)
|
55
60
|
assert(sample_data.min >= MIN_SAMPLE_8BIT)
|
@@ -59,7 +64,7 @@ class SongTest < Test::Unit::TestCase
|
|
59
64
|
}
|
60
65
|
assert(all_are_fixnums)
|
61
66
|
|
62
|
-
kit.add("mono16", "
|
67
|
+
kit.add("mono16", "bass_mono_16.wav")
|
63
68
|
sample_data = kit.get_sample_data("mono8")
|
64
69
|
assert(sample_data.max > MAX_SAMPLE_8BIT)
|
65
70
|
assert(sample_data.min < MIN_SAMPLE_8BIT)
|
@@ -69,7 +74,7 @@ class SongTest < Test::Unit::TestCase
|
|
69
74
|
}
|
70
75
|
assert(all_are_fixnums)
|
71
76
|
|
72
|
-
kit.add("stereo16", "
|
77
|
+
kit.add("stereo16", "bass_stereo_16.wav")
|
73
78
|
sample_data = kit.get_sample_data("stereo16")
|
74
79
|
assert(sample_data.flatten.max > MAX_SAMPLE_8BIT)
|
75
80
|
assert(sample_data.flatten.min < MIN_SAMPLE_8BIT)
|
@@ -83,9 +88,9 @@ class SongTest < Test::Unit::TestCase
|
|
83
88
|
|
84
89
|
# Test adding sounds with progressively lower bits per sample and num channels.
|
85
90
|
# Verify that sample data bits per sample and num channels doesn't go down.
|
86
|
-
kit = Kit.new()
|
91
|
+
kit = Kit.new("test/sounds")
|
87
92
|
|
88
|
-
kit.add("stereo16", "
|
93
|
+
kit.add("stereo16", "bass_stereo_16.wav")
|
89
94
|
sample_data = kit.get_sample_data("stereo16")
|
90
95
|
assert(sample_data.flatten.max > MAX_SAMPLE_8BIT)
|
91
96
|
assert(sample_data.flatten.min < MIN_SAMPLE_8BIT)
|
@@ -96,7 +101,7 @@ class SongTest < Test::Unit::TestCase
|
|
96
101
|
assert(all_are_arrays)
|
97
102
|
assert(sample_data.first.length == 2)
|
98
103
|
|
99
|
-
kit.add("mono16", "
|
104
|
+
kit.add("mono16", "bass_mono_16.wav")
|
100
105
|
sample_data = kit.get_sample_data("mono16")
|
101
106
|
assert(sample_data.flatten.max > MAX_SAMPLE_8BIT)
|
102
107
|
assert(sample_data.flatten.min < MIN_SAMPLE_8BIT)
|
@@ -107,7 +112,7 @@ class SongTest < Test::Unit::TestCase
|
|
107
112
|
assert(all_are_arrays)
|
108
113
|
assert(sample_data.first.length == 2)
|
109
114
|
|
110
|
-
kit.add("mono8", "
|
115
|
+
kit.add("mono8", "bass_mono_8.wav")
|
111
116
|
sample_data = kit.get_sample_data("mono8")
|
112
117
|
assert(sample_data.flatten.max > MAX_SAMPLE_8BIT)
|
113
118
|
assert(sample_data.flatten.min < MIN_SAMPLE_8BIT)
|
data/test/pattern_test.rb
CHANGED
@@ -7,11 +7,11 @@ class PatternTest < Test::Unit::TestCase
|
|
7
7
|
SECONDS_IN_MINUTE = 60.0
|
8
8
|
|
9
9
|
def generate_test_data
|
10
|
-
kit = Kit.new()
|
11
|
-
kit.add("bass.wav", "
|
12
|
-
kit.add("snare.wav", "
|
13
|
-
kit.add("hh_closed.wav", "
|
14
|
-
kit.add("hh_open.wav", "
|
10
|
+
kit = Kit.new("test/sounds")
|
11
|
+
kit.add("bass.wav", "bass_mono_8.wav")
|
12
|
+
kit.add("snare.wav", "snare_mono_8.wav")
|
13
|
+
kit.add("hh_closed.wav", "hh_closed_mono_8.wav")
|
14
|
+
kit.add("hh_open.wav", "hh_open_mono_8.wav")
|
15
15
|
|
16
16
|
test_patterns = []
|
17
17
|
|
data/test/song_test.rb
CHANGED
@@ -2,41 +2,26 @@ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
|
2
2
|
|
3
3
|
require 'test/includes'
|
4
4
|
|
5
|
-
class MockSong < Song
|
6
|
-
attr_reader :patterns
|
7
|
-
attr_accessor :kit
|
8
|
-
end
|
9
|
-
|
10
5
|
class SongTest < Test::Unit::TestCase
|
11
6
|
DEFAULT_TEMPO = 120
|
12
7
|
|
13
8
|
def generate_test_data
|
14
|
-
kit = Kit.new()
|
15
|
-
kit.add("bass.wav", "
|
16
|
-
kit.add("snare.wav", "
|
17
|
-
kit.add("hh_closed.wav", "
|
18
|
-
kit.add("ride.wav", "
|
9
|
+
kit = Kit.new("test/sounds")
|
10
|
+
kit.add("bass.wav", "bass_mono_8.wav")
|
11
|
+
kit.add("snare.wav", "snare_mono_8.wav")
|
12
|
+
kit.add("hh_closed.wav", "hh_closed_mono_8.wav")
|
13
|
+
kit.add("ride.wav", "ride_mono_8.wav")
|
19
14
|
|
20
|
-
test_songs =
|
15
|
+
test_songs = SongParserTest.generate_test_data()
|
21
16
|
|
22
|
-
test_songs[:blank] =
|
17
|
+
test_songs[:blank] = Song.new("test/sounds")
|
23
18
|
|
24
|
-
test_songs[:no_structure] =
|
19
|
+
test_songs[:no_structure] = Song.new("test/sounds")
|
25
20
|
verse = test_songs[:no_structure].pattern :verse
|
26
21
|
verse.track "bass.wav", kit.get_sample_data("bass.wav"), "X.......X......."
|
27
22
|
verse.track "snare.wav", kit.get_sample_data("snare.wav"), "....X.......X..."
|
28
23
|
verse.track "hh_closed.wav", kit.get_sample_data("hh_closed.wav"), "X.X.X.X.X.X.X.X."
|
29
24
|
|
30
|
-
repeats_not_specified_yaml = "
|
31
|
-
Song:
|
32
|
-
Tempo: 100
|
33
|
-
Structure:
|
34
|
-
- Verse
|
35
|
-
|
36
|
-
Verse:
|
37
|
-
- test/sounds/bass_mono_8.wav: X"
|
38
|
-
test_songs[:repeats_not_specified] = MockSong.new(File.dirname(__FILE__) + "/..", repeats_not_specified_yaml)
|
39
|
-
|
40
25
|
overflow_yaml = "
|
41
26
|
Song:
|
42
27
|
Tempo: 100
|
@@ -45,9 +30,9 @@ Song:
|
|
45
30
|
|
46
31
|
Verse:
|
47
32
|
- test/sounds/snare_mono_8.wav: ...X"
|
48
|
-
test_songs[:overflow] =
|
33
|
+
test_songs[:overflow] = SongParser.new().parse(File.dirname(__FILE__) + "/..", overflow_yaml)
|
49
34
|
|
50
|
-
test_songs[:from_code] =
|
35
|
+
test_songs[:from_code] = Song.new("test/sounds")
|
51
36
|
verse = test_songs[:from_code].pattern :verse
|
52
37
|
verse.track "bass.wav", kit.get_sample_data("bass.wav"), "X.......X......."
|
53
38
|
verse.track "snare.wav", kit.get_sample_data("snare.wav"), "....X.......X..."
|
@@ -59,91 +44,20 @@ Verse:
|
|
59
44
|
test_songs[:from_code].structure = [:verse, :chorus, :verse, :chorus, :chorus]
|
60
45
|
test_songs[:from_code].kit = kit
|
61
46
|
|
62
|
-
valid_yaml_string = "# An example song
|
63
|
-
|
64
|
-
Song:
|
65
|
-
Tempo: 99
|
66
|
-
Structure:
|
67
|
-
- Verse: x2
|
68
|
-
- Chorus: x2
|
69
|
-
- Verse: x2
|
70
|
-
- Chorus: x4
|
71
|
-
- Bridge: x1
|
72
|
-
- Chorus: x4
|
73
|
-
|
74
|
-
Verse:
|
75
|
-
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X...
|
76
|
-
- test/sounds/snare_mono_8.wav: ..X...X...X...X.X...X...X...X...
|
77
|
-
# Here is a comment
|
78
|
-
- test/sounds/hh_closed_mono_8.wav: X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.
|
79
|
-
- test/sounds/hh_open_mono_8.wav: X...............X..............X
|
80
|
-
# Here is another comment
|
81
|
-
Chorus:
|
82
|
-
- test/sounds/bass_mono_8.wav: X...X...XXXXXXXXX...X...X...X...
|
83
|
-
- test/sounds/snare_mono_8.wav: ...................X...X...X...X
|
84
|
-
- test/sounds/hh_closed_mono_8.wav: X.X.XXX.X.X.XXX.X.X.XXX.X.X.XXX. # It's comment time
|
85
|
-
- test/sounds/hh_open_mono_8.wav: ........X.......X.......X.......
|
86
|
-
- test/sounds/ride_mono_8.wav: ....X...................X.......
|
87
|
-
|
88
|
-
|
89
|
-
Bridge:
|
90
|
-
- test/sounds/hh_closed_mono_8.wav: XX.XXX.XXX.XXX.XXX.XXX.XXX.XXX.X"
|
91
|
-
|
92
|
-
test_songs[:from_valid_yaml_string] = MockSong.new(File.dirname(__FILE__) + "/..", valid_yaml_string)
|
93
|
-
|
94
47
|
return test_songs
|
95
48
|
end
|
96
49
|
|
97
50
|
def test_initialize
|
98
|
-
test_songs = generate_test_data
|
51
|
+
test_songs = generate_test_data()
|
99
52
|
|
100
53
|
assert_equal(test_songs[:blank].structure, [])
|
101
54
|
assert_equal(test_songs[:blank].tick_sample_length, (Song::SAMPLE_RATE * Song::SECONDS_PER_MINUTE) / DEFAULT_TEMPO / 4.0)
|
55
|
+
|
102
56
|
assert_equal(test_songs[:no_structure].structure, [])
|
103
57
|
assert_equal(test_songs[:no_structure].tick_sample_length, (Song::SAMPLE_RATE * Song::SECONDS_PER_MINUTE) / DEFAULT_TEMPO / 4.0)
|
58
|
+
|
104
59
|
assert_equal(test_songs[:from_code].structure, [:verse, :chorus, :verse, :chorus, :chorus])
|
105
60
|
assert_equal(test_songs[:from_code].tick_sample_length, (Song::SAMPLE_RATE * Song::SECONDS_PER_MINUTE) / DEFAULT_TEMPO / 4.0)
|
106
|
-
|
107
|
-
assert_equal(test_songs[:from_valid_yaml_string].structure, [:verse, :verse, :chorus, :chorus, :verse, :verse, :chorus, :chorus, :chorus, :chorus, :bridge, :chorus, :chorus, :chorus, :chorus])
|
108
|
-
assert_equal(test_songs[:from_valid_yaml_string].tempo, 99)
|
109
|
-
assert_equal(test_songs[:from_valid_yaml_string].tick_sample_length, (Song::SAMPLE_RATE * Song::SECONDS_PER_MINUTE) / 99 / 4.0)
|
110
|
-
assert_equal(test_songs[:from_valid_yaml_string].patterns.keys.map{|key| key.to_s}.sort, ["bridge", "chorus", "verse"])
|
111
|
-
assert_equal(test_songs[:from_valid_yaml_string].patterns[:verse].tracks.length, 4)
|
112
|
-
assert_equal(test_songs[:from_valid_yaml_string].patterns[:chorus].tracks.length, 5)
|
113
|
-
assert_equal(test_songs[:from_valid_yaml_string].patterns[:bridge].tracks.length, 1)
|
114
|
-
end
|
115
|
-
|
116
|
-
def test_invalid_initialize
|
117
|
-
invalid_tempo_yaml_string = "# Invalid tempo song
|
118
|
-
Song:
|
119
|
-
Tempo: 100a
|
120
|
-
Structure:
|
121
|
-
- Verse: x2
|
122
|
-
|
123
|
-
Verse:
|
124
|
-
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
125
|
-
assert_raise(SongParseError) { song = MockSong.new(File.dirname(__FILE__) + "/..", invalid_tempo_yaml_string) }
|
126
|
-
|
127
|
-
invalid_structure_yaml_string = "# Invalid structure song
|
128
|
-
Song:
|
129
|
-
Tempo: 100
|
130
|
-
Structure:
|
131
|
-
- Verse: x2
|
132
|
-
- Chorus: x1
|
133
|
-
|
134
|
-
Verse:
|
135
|
-
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
136
|
-
assert_raise(SongParseError) { song = MockSong.new(File.dirname(__FILE__) + "/..", invalid_structure_yaml_string) }
|
137
|
-
|
138
|
-
invalid_repeats_yaml_string = " # Invalid structure song
|
139
|
-
Song:
|
140
|
-
Tempo: 100
|
141
|
-
Structure:
|
142
|
-
- Verse: x2a
|
143
|
-
|
144
|
-
Verse:
|
145
|
-
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
146
|
-
assert_raise(SongParseError) { song = MockSong.new(File.dirname(__FILE__) + "/..", invalid_repeats_yaml_string) }
|
147
61
|
end
|
148
62
|
|
149
63
|
def test_total_tracks
|
@@ -0,0 +1,207 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
|
3
|
+
require 'test/includes'
|
4
|
+
|
5
|
+
class SongParserTest < Test::Unit::TestCase
|
6
|
+
def self.generate_test_data
|
7
|
+
kit = Kit.new("test/sounds")
|
8
|
+
kit.add("bass.wav", "bass_mono_8.wav")
|
9
|
+
kit.add("snare.wav", "snare_mono_8.wav")
|
10
|
+
kit.add("hh_closed.wav", "hh_closed_mono_8.wav")
|
11
|
+
kit.add("ride.wav", "ride_mono_8.wav")
|
12
|
+
|
13
|
+
test_songs = {}
|
14
|
+
base_path = File.dirname(__FILE__) + "/.."
|
15
|
+
|
16
|
+
no_tempo_yaml = "
|
17
|
+
Song:
|
18
|
+
Structure:
|
19
|
+
- Verse: x1
|
20
|
+
|
21
|
+
Verse:
|
22
|
+
- test/sounds/bass_mono_8.wav: X"
|
23
|
+
test_songs[:no_tempo] = SongParser.new().parse(base_path, no_tempo_yaml)
|
24
|
+
|
25
|
+
repeats_not_specified_yaml = "
|
26
|
+
Song:
|
27
|
+
Tempo: 100
|
28
|
+
Structure:
|
29
|
+
- Verse
|
30
|
+
|
31
|
+
Verse:
|
32
|
+
- test/sounds/bass_mono_8.wav: X"
|
33
|
+
test_songs[:repeats_not_specified] = SongParser.new().parse(base_path, repeats_not_specified_yaml)
|
34
|
+
|
35
|
+
overflow_yaml = "
|
36
|
+
Song:
|
37
|
+
Tempo: 100
|
38
|
+
Structure:
|
39
|
+
- Verse: x2
|
40
|
+
|
41
|
+
Verse:
|
42
|
+
- test/sounds/snare_mono_8.wav: ...X"
|
43
|
+
test_songs[:overflow] = SongParser.new().parse(base_path, overflow_yaml)
|
44
|
+
|
45
|
+
valid_yaml_string = "# An example song
|
46
|
+
|
47
|
+
Song:
|
48
|
+
Tempo: 99
|
49
|
+
Structure:
|
50
|
+
- Verse: x2
|
51
|
+
- Chorus: x2
|
52
|
+
- Verse: x2
|
53
|
+
- Chorus: x4
|
54
|
+
- Bridge: x1
|
55
|
+
- Undefined: x0 # This is legal as long as num repeats is 0.
|
56
|
+
- Chorus: x4
|
57
|
+
|
58
|
+
Verse:
|
59
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X...
|
60
|
+
- test/sounds/snare_mono_8.wav: ..X...X...X...X.X...X...X...X...
|
61
|
+
# Here is a comment
|
62
|
+
- test/sounds/hh_closed_mono_8.wav: X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.
|
63
|
+
- test/sounds/hh_open_mono_8.wav: X...............X..............X
|
64
|
+
# Here is another comment
|
65
|
+
Chorus:
|
66
|
+
- test/sounds/bass_mono_8.wav: X...X...XXXXXXXXX...X...X...X...
|
67
|
+
- test/sounds/snare_mono_8.wav: ...................X...X...X...X
|
68
|
+
- test/sounds/hh_closed_mono_8.wav: X.X.XXX.X.X.XXX.X.X.XXX.X.X.XXX. # It's comment time
|
69
|
+
- test/sounds/hh_open_mono_8.wav: ........X.......X.......X.......
|
70
|
+
- test/sounds/ride_mono_8.wav: ....X...................X.......
|
71
|
+
|
72
|
+
|
73
|
+
Bridge:
|
74
|
+
- test/sounds/hh_closed_mono_8.wav: XX.XXX.XXX.XXX.XXX.XXX.XXX.XXX.X"
|
75
|
+
test_songs[:from_valid_yaml_string] = SongParser.new().parse(base_path, valid_yaml_string)
|
76
|
+
|
77
|
+
valid_yaml_string_with_kit = "# An example song
|
78
|
+
|
79
|
+
Song:
|
80
|
+
Tempo: 99
|
81
|
+
Kit:
|
82
|
+
- bass: test/sounds/bass_mono_8.wav
|
83
|
+
- snare: test/sounds/snare_mono_8.wav
|
84
|
+
- hhclosed: test/sounds/hh_closed_mono_8.wav
|
85
|
+
- hhopen: test/sounds/hh_open_mono_8.wav
|
86
|
+
Structure:
|
87
|
+
- Verse: x2
|
88
|
+
- Chorus: x2
|
89
|
+
- Verse: x2
|
90
|
+
- Chorus: x4
|
91
|
+
- Bridge: x1
|
92
|
+
- Undefined: x0 # This is legal as long as num repeats is 0.
|
93
|
+
- Chorus: x4
|
94
|
+
|
95
|
+
Verse:
|
96
|
+
- base: X...X...X...XX..X...X...XX..X...
|
97
|
+
- snare: ..X...X...X...X.X...X...X...X...
|
98
|
+
# Here is a comment
|
99
|
+
- hhclosed: X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.
|
100
|
+
- hhopen: X...............X..............X
|
101
|
+
# Here is another comment
|
102
|
+
Chorus:
|
103
|
+
- bass: X...X...XXXXXXXXX...X...X...X...
|
104
|
+
- snare: ...................X...X...X...X
|
105
|
+
- test/sounds/hh_closed_mono_8.wav: X.X.XXX.X.X.XXX.X.X.XXX.X.X.XXX. # It's comment time
|
106
|
+
- hhopen: ........X.......X.......X.......
|
107
|
+
- test/sounds/ride_mono_8.wav: ....X...................X.......
|
108
|
+
|
109
|
+
Bridge:
|
110
|
+
- hhclosed: XX.XXX.XXX.XXX.XXX.XXX.XXX.XXX.X"
|
111
|
+
test_songs[:from_valid_yaml_string_with_kit] = SongParser.new().parse(base_path, valid_yaml_string)
|
112
|
+
|
113
|
+
return test_songs
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_valid_initialize
|
117
|
+
test_songs = SongParserTest.generate_test_data()
|
118
|
+
|
119
|
+
assert_equal(test_songs[:no_tempo].tempo, 120)
|
120
|
+
assert_equal(test_songs[:no_tempo].structure, [:verse])
|
121
|
+
|
122
|
+
assert_equal(test_songs[:repeats_not_specified].tempo, 100)
|
123
|
+
assert_equal(test_songs[:repeats_not_specified].structure, [:verse])
|
124
|
+
|
125
|
+
# These two songs should be the same, except that one uses a kit in the song header
|
126
|
+
# and the other doesn't.
|
127
|
+
[:from_valid_yaml_string, :from_valid_yaml_string_with_kit].each {|song_key|
|
128
|
+
song = test_songs[song_key]
|
129
|
+
assert_equal(song.structure, [:verse, :verse, :chorus, :chorus, :verse, :verse, :chorus, :chorus, :chorus, :chorus, :bridge, :chorus, :chorus, :chorus, :chorus])
|
130
|
+
assert_equal(song.tempo, 99)
|
131
|
+
assert_equal(song.tick_sample_length, (Song::SAMPLE_RATE * Song::SECONDS_PER_MINUTE) / 99 / 4.0)
|
132
|
+
assert_equal(song.patterns.keys.map{|key| key.to_s}.sort, ["bridge", "chorus", "verse"])
|
133
|
+
assert_equal(song.patterns[:verse].tracks.length, 4)
|
134
|
+
assert_equal(song.patterns[:chorus].tracks.length, 5)
|
135
|
+
assert_equal(song.patterns[:bridge].tracks.length, 1)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_invalid_initialize
|
140
|
+
no_header_yaml_string = "# Song with no header
|
141
|
+
Verse:
|
142
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
143
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", no_header_yaml_string) }
|
144
|
+
|
145
|
+
sound_doesnt_exist_yaml_string = "# Song with non-existent sound
|
146
|
+
Song:
|
147
|
+
Tempo: 100
|
148
|
+
Structure:
|
149
|
+
- Verse: x1
|
150
|
+
|
151
|
+
Verse:
|
152
|
+
- test/sounds/i_do_not_exist.wav: X...X..."
|
153
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", sound_doesnt_exist_yaml_string) }
|
154
|
+
|
155
|
+
|
156
|
+
sound_doesnt_exist_in_kit_yaml_string = "# Song with non-existent sound in Kit
|
157
|
+
Song:
|
158
|
+
Tempo: 100
|
159
|
+
Structure:
|
160
|
+
- Verse: x1
|
161
|
+
Kit:
|
162
|
+
- bad: test/sounds/i_do_not_exist.wav
|
163
|
+
|
164
|
+
Verse:
|
165
|
+
- bad: X...X..."
|
166
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", sound_doesnt_exist_in_kit_yaml_string) }
|
167
|
+
|
168
|
+
invalid_tempo_yaml_string = "# Song with invalid tempo
|
169
|
+
Song:
|
170
|
+
Tempo: 100a
|
171
|
+
Structure:
|
172
|
+
- Verse: x2
|
173
|
+
|
174
|
+
Verse:
|
175
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
176
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", invalid_tempo_yaml_string) }
|
177
|
+
|
178
|
+
invalid_structure_yaml_string = "# Song whose structure references non-existent pattern
|
179
|
+
Song:
|
180
|
+
Tempo: 100
|
181
|
+
Structure:
|
182
|
+
- Verse: x2
|
183
|
+
- Chorus: x1
|
184
|
+
|
185
|
+
Verse:
|
186
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
187
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", invalid_structure_yaml_string) }
|
188
|
+
|
189
|
+
no_structure_yaml_string = "# Song without a structure section in the header
|
190
|
+
Song:
|
191
|
+
Tempo: 100
|
192
|
+
|
193
|
+
Verse:
|
194
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
195
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", no_structure_yaml_string) }
|
196
|
+
|
197
|
+
invalid_repeats_yaml_string = "# Song with invalid number of repeats for pattern
|
198
|
+
Song:
|
199
|
+
Tempo: 100
|
200
|
+
Structure:
|
201
|
+
- Verse: x2a
|
202
|
+
|
203
|
+
Verse:
|
204
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X..."
|
205
|
+
assert_raise(SongParseError) { song = SongParser.new().parse(File.dirname(__FILE__) + "/..", invalid_repeats_yaml_string) }
|
206
|
+
end
|
207
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Strait
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-04-12 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -36,12 +36,14 @@ files:
|
|
36
36
|
- lib/kit.rb
|
37
37
|
- lib/pattern.rb
|
38
38
|
- lib/song.rb
|
39
|
+
- lib/songparser.rb
|
39
40
|
- lib/track.rb
|
40
41
|
- bin/beats
|
41
42
|
- test/includes.rb
|
42
43
|
- test/kit_test.rb
|
43
44
|
- test/pattern_test.rb
|
44
45
|
- test/song_test.rb
|
46
|
+
- test/songparser_test.rb
|
45
47
|
- test/sounds/bass_mono_16.wav
|
46
48
|
- test/sounds/bass_mono_8.wav
|
47
49
|
- test/sounds/bass_stereo_16.wav
|
@@ -83,6 +85,7 @@ test_files:
|
|
83
85
|
- test/kit_test.rb
|
84
86
|
- test/pattern_test.rb
|
85
87
|
- test/song_test.rb
|
88
|
+
- test/songparser_test.rb
|
86
89
|
- test/sounds/bass_mono_16.wav
|
87
90
|
- test/sounds/bass_mono_8.wav
|
88
91
|
- test/sounds/bass_stereo_16.wav
|