beats 2.1.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +2 -2
- data/README.markdown +17 -80
- data/bin/beats +2 -7
- data/lib/beats.rb +12 -6
- data/lib/beats/{audioengine.rb → audio_engine.rb} +7 -8
- data/lib/beats/{audioutils.rb → audio_utils.rb} +35 -17
- data/lib/beats/{beatsrunner.rb → beats_runner.rb} +2 -2
- data/lib/beats/kit.rb +6 -5
- data/lib/beats/kit_builder.rb +12 -8
- data/lib/beats/pattern.rb +7 -16
- data/lib/beats/song.rb +7 -8
- data/lib/beats/{songoptimizer.rb → song_optimizer.rb} +11 -8
- data/lib/beats/{songparser.rb → song_parser.rb} +46 -48
- data/lib/beats/track.rb +14 -17
- data/lib/beats/transforms/song_swinger.rb +7 -5
- data/lib/wavefile/{cachingwriter.rb → caching_writer.rb} +2 -2
- data/test/{audioengine_test.rb → audio_engine_test.rb} +49 -46
- data/test/audio_utils_test.rb +86 -0
- data/test/{cachingwriter_test.rb → caching_writer_test.rb} +0 -0
- data/test/fixtures/invalid/sound_in_kit_wrong_format.txt +1 -1
- data/test/fixtures/invalid/sound_in_track_wrong_format.txt +1 -1
- data/test/fixtures/valid/empty_kit.txt +14 -0
- data/test/fixtures/valid/example_song_header_different_capitalization.txt +21 -0
- data/test/fixtures/valid/multiple_patterns_same_name.txt +31 -0
- data/test/fixtures/valid/multiple_song_header_sections.txt +29 -0
- data/test/includes.rb +0 -9
- data/test/kit_builder_test.rb +20 -0
- data/test/kit_test.rb +7 -0
- data/test/pattern_test.rb +86 -74
- data/test/{songoptimizer_test.rb → song_optimizer_test.rb} +5 -8
- data/test/{songparser_test.rb → song_parser_test.rb} +109 -13
- data/test/song_swinger_test.rb +6 -4
- data/test/song_test.rb +32 -14
- data/test/sounds/agogo_high_stereo_16.wav +0 -0
- data/test/sounds/agogo_low_stereo_16.wav +0 -0
- data/test/sounds/bass2_stereo_16.wav +0 -0
- data/test/sounds/bass_stereo_16.wav +0 -0
- data/test/sounds/clave_high_stereo_16.wav +0 -0
- data/test/sounds/clave_low_stereo_16.wav +0 -0
- data/test/sounds/conga_high_stereo_16.wav +0 -0
- data/test/sounds/conga_low_stereo_16.wav +0 -0
- data/test/sounds/cowbell_high_stereo_16.wav +0 -0
- data/test/sounds/cowbell_low_stereo_16.wav +0 -0
- data/test/sounds/hh_closed_stereo_16.wav +0 -0
- data/test/sounds/hh_open_stereo_16.wav +0 -0
- data/test/sounds/ride_stereo_16.wav +0 -0
- data/test/sounds/rim_stereo_16.wav +0 -0
- data/test/sounds/snare2_stereo_16.wav +0 -0
- data/test/sounds/snare_stereo_16.wav +0 -0
- data/test/sounds/tom1_stereo_16.wav +0 -0
- data/test/sounds/tom2_stereo_16.wav +0 -0
- data/test/sounds/tom3_stereo_16.wav +0 -0
- data/test/sounds/tom4_stereo_16.wav +0 -0
- data/test/track_test.rb +51 -5
- metadata +184 -176
- data/test/audioutils_test.rb +0 -46
data/lib/beats/song.rb
CHANGED
@@ -18,8 +18,8 @@ module Beats
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Adds a new pattern to the song, with the specified name.
|
21
|
-
def pattern(name)
|
22
|
-
@patterns[name] = Pattern.new(name)
|
21
|
+
def pattern(name, tracks=[])
|
22
|
+
@patterns[name] = Pattern.new(name, tracks)
|
23
23
|
end
|
24
24
|
|
25
25
|
|
@@ -75,15 +75,14 @@ module Beats
|
|
75
75
|
track_names.each do |track_name|
|
76
76
|
new_song = copy_ignoring_patterns_and_flow
|
77
77
|
|
78
|
-
@patterns.each do |
|
79
|
-
new_pattern = new_song.pattern(name)
|
80
|
-
|
78
|
+
@patterns.each do |pattern_name, original_pattern|
|
81
79
|
if original_pattern.tracks.has_key?(track_name)
|
82
|
-
|
83
|
-
new_pattern.track(original_track.name, original_track.rhythm)
|
80
|
+
new_track = original_pattern.tracks[track_name]
|
84
81
|
else
|
85
|
-
|
82
|
+
new_track = Track.new(track_name, Track::REST * original_pattern.step_count)
|
86
83
|
end
|
84
|
+
|
85
|
+
new_song.pattern(pattern_name, [new_track])
|
87
86
|
end
|
88
87
|
|
89
88
|
new_song.flow = @flow
|
@@ -57,7 +57,7 @@ module Beats
|
|
57
57
|
# Note that if a track in a sub-divided pattern has no triggers (such as track2 in the
|
58
58
|
# 2nd pattern above), it will not be included in the new pattern.
|
59
59
|
def subdivide_song_patterns(original_song, optimized_song, max_pattern_length)
|
60
|
-
|
60
|
+
blank_track_rhythm = Track::REST * max_pattern_length
|
61
61
|
|
62
62
|
# For each pattern, add a new pattern to new song every max_pattern_length steps
|
63
63
|
optimized_flow = {}
|
@@ -68,23 +68,26 @@ module Beats
|
|
68
68
|
while(pattern.tracks.values.first.rhythm[step_index] != nil) do
|
69
69
|
# TODO: Is this pattern 100% sufficient to prevent collisions between subdivided
|
70
70
|
# pattern names and existing patterns with numeric suffixes?
|
71
|
-
|
72
|
-
|
71
|
+
new_pattern_name = "#{pattern.name}_#{step_index}".to_sym
|
72
|
+
new_tracks = []
|
73
|
+
optimized_flow[pattern.name] << new_pattern_name
|
73
74
|
pattern.tracks.values.each do |track|
|
74
|
-
|
75
|
+
sub_track_rhythm = track.rhythm[step_index...(step_index + max_pattern_length)]
|
75
76
|
|
76
|
-
if
|
77
|
-
|
77
|
+
if sub_track_rhythm != blank_track_rhythm
|
78
|
+
new_tracks << Track.new(track.name, sub_track_rhythm)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
81
82
|
# If no track has a trigger during this step pattern, add a blank track.
|
82
83
|
# Otherwise, this pattern will have no steps, and no sound will be generated,
|
83
84
|
# causing the pattern to be "compacted away".
|
84
|
-
if
|
85
|
-
|
85
|
+
if new_tracks.empty?
|
86
|
+
new_tracks << Track.new(Kit::PLACEHOLDER_TRACK_NAME, blank_track_rhythm)
|
86
87
|
end
|
87
88
|
|
89
|
+
optimized_song.pattern(new_pattern_name, new_tracks)
|
90
|
+
|
88
91
|
step_index += max_pattern_length
|
89
92
|
end
|
90
93
|
end
|
@@ -6,23 +6,10 @@ module Beats
|
|
6
6
|
# The sole public method is parse(). It takes a raw YAML string and returns a Song and
|
7
7
|
# Kit object (or raises an error if the YAML string couldn't be parsed correctly).
|
8
8
|
class SongParser
|
9
|
-
class ParseError <
|
10
|
-
|
11
|
-
NO_SONG_HEADER_ERROR_MSG =
|
12
|
-
"Song must have a header. Here's an example:
|
13
|
-
|
14
|
-
Song:
|
15
|
-
Tempo: 120
|
16
|
-
Flow:
|
17
|
-
- Verse: x2
|
18
|
-
- Chorus: x2"
|
19
|
-
|
20
|
-
def initialize
|
21
|
-
end
|
22
|
-
|
9
|
+
class ParseError < StandardError; end
|
23
10
|
|
24
11
|
# Parses a raw YAML song definition and converts it into a Song and Kit object.
|
25
|
-
def parse(base_path, raw_yaml_string)
|
12
|
+
def self.parse(base_path, raw_yaml_string)
|
26
13
|
raw_song_components = hashify_raw_yaml(raw_yaml_string)
|
27
14
|
|
28
15
|
unless raw_song_components[:folder].nil?
|
@@ -85,8 +72,16 @@ module Beats
|
|
85
72
|
|
86
73
|
private
|
87
74
|
|
75
|
+
NO_SONG_HEADER_ERROR_MSG =
|
76
|
+
"Song must have a header. Here's an example:
|
77
|
+
|
78
|
+
Song:
|
79
|
+
Tempo: 120
|
80
|
+
Flow:
|
81
|
+
- Verse: x2
|
82
|
+
- Chorus: x2"
|
88
83
|
|
89
|
-
def hashify_raw_yaml(raw_yaml_string)
|
84
|
+
def self.hashify_raw_yaml(raw_yaml_string)
|
90
85
|
begin
|
91
86
|
raw_song_definition = YAML.load(raw_yaml_string)
|
92
87
|
rescue Psych::SyntaxError => detail
|
@@ -95,38 +90,39 @@ module Beats
|
|
95
90
|
raise ParseError, "Syntax error in YAML file: #{detail}"
|
96
91
|
end
|
97
92
|
|
98
|
-
|
99
|
-
raw_song_components[:full_definition] = downcase_hash_keys(raw_song_definition)
|
93
|
+
header_keys = raw_song_definition.keys.select {|key| key.downcase == "song" }
|
100
94
|
|
101
|
-
|
102
|
-
raw_song_components[:header] = downcase_hash_keys(raw_song_components[:full_definition]["song"])
|
103
|
-
else
|
95
|
+
if header_keys.empty?
|
104
96
|
raise ParseError, NO_SONG_HEADER_ERROR_MSG
|
97
|
+
elsif header_keys.length > 1
|
98
|
+
# In theory, this branch should never be reached, due the YAML hash mappings
|
99
|
+
# not allowing duplicate keys?
|
100
|
+
raise ParseError, "Song has multiple 'Song' sections, it should only have 1."
|
101
|
+
else
|
102
|
+
header = downcase_hash_keys(raw_song_definition.delete(header_keys.first))
|
105
103
|
end
|
106
|
-
raw_song_components[:tempo] = raw_song_components[:header]["tempo"]
|
107
|
-
raw_song_components[:folder] = raw_song_components[:header]["folder"]
|
108
|
-
raw_song_components[:kit] = raw_song_components[:header]["kit"]
|
109
104
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
105
|
+
{
|
106
|
+
tempo: header["tempo"],
|
107
|
+
folder: header["folder"],
|
108
|
+
kit: header["kit"],
|
109
|
+
flow: header["flow"],
|
110
|
+
swing: header["swing"],
|
111
|
+
patterns: raw_song_definition,
|
112
|
+
}
|
116
113
|
end
|
117
114
|
|
118
|
-
def add_kit_sounds_from_kit(kit_builder, raw_kit)
|
115
|
+
def self.add_kit_sounds_from_kit(kit_builder, raw_kit)
|
116
|
+
return if raw_kit.nil?
|
117
|
+
|
119
118
|
# Add sounds defined in the Kit section of the song header
|
120
119
|
# Converts [{a=>1}, {b=>2}, {c=>3}] from raw YAML to {a=>1, b=>2, c=>3}
|
121
|
-
|
122
|
-
|
123
|
-
raw_kit.each do |kit_item|
|
124
|
-
kit_builder.add_item(kit_item.keys.first, kit_item.values.first)
|
125
|
-
end
|
120
|
+
raw_kit.each do |kit_item|
|
121
|
+
kit_builder.add_item(kit_item.keys.first, kit_item.values.first)
|
126
122
|
end
|
127
123
|
end
|
128
124
|
|
129
|
-
def add_kit_sounds_from_patterns(kit_builder, patterns)
|
125
|
+
def self.add_kit_sounds_from_patterns(kit_builder, patterns)
|
130
126
|
# Add sounds not defined in Kit section, but used in individual tracks
|
131
127
|
patterns.each do |pattern_name, pattern|
|
132
128
|
pattern.tracks.each do |track_name, track|
|
@@ -139,15 +135,14 @@ module Beats
|
|
139
135
|
end
|
140
136
|
end
|
141
137
|
|
142
|
-
def add_patterns_to_song(song, kit_builder, raw_patterns)
|
138
|
+
def self.add_patterns_to_song(song, kit_builder, raw_patterns)
|
143
139
|
raw_patterns.each do |pattern_name, raw_tracks|
|
144
140
|
if raw_tracks.nil?
|
145
|
-
# TODO: Use correct capitalization of pattern name in error message
|
146
141
|
# TODO: Possibly allow if pattern not referenced in the Flow, or has 0 repeats?
|
147
142
|
raise ParseError, "Pattern '#{pattern_name}' has no tracks. It needs at least one."
|
148
143
|
end
|
149
144
|
|
150
|
-
|
145
|
+
tracks = []
|
151
146
|
|
152
147
|
raw_tracks.each do |raw_track|
|
153
148
|
track_names = raw_track.keys.first
|
@@ -158,7 +153,7 @@ module Beats
|
|
158
153
|
|
159
154
|
track_names = Array(track_names)
|
160
155
|
if track_names.empty?
|
161
|
-
raise ParseError, "Pattern '#{pattern_name}'
|
156
|
+
raise ParseError, "Pattern '#{pattern_name}' uses an empty composite sound (i.e. \"[]\"), which is not valid."
|
162
157
|
end
|
163
158
|
|
164
159
|
track_names.map! do |track_name|
|
@@ -170,17 +165,19 @@ module Beats
|
|
170
165
|
track_names.flatten!
|
171
166
|
|
172
167
|
track_names.each do |track_name|
|
173
|
-
|
168
|
+
tracks << Track.new(track_name, rhythm)
|
174
169
|
end
|
175
170
|
end
|
171
|
+
|
172
|
+
song.pattern(pattern_name.downcase.to_sym, tracks)
|
176
173
|
end
|
177
174
|
end
|
178
175
|
|
179
176
|
|
180
|
-
def set_song_flow(song, raw_flow)
|
177
|
+
def self.set_song_flow(song, raw_flow)
|
181
178
|
flow = []
|
182
179
|
|
183
|
-
raw_flow.each
|
180
|
+
raw_flow.each do |pattern_item|
|
184
181
|
if pattern_item.class == String
|
185
182
|
pattern_item = {pattern_item => "x1"}
|
186
183
|
end
|
@@ -203,19 +200,20 @@ module Beats
|
|
203
200
|
# This test is purposefully designed to only throw an error if the number of repeats is greater
|
204
201
|
# than 0. This allows you to specify an undefined pattern in the flow with "x0" repeats.
|
205
202
|
# This can be convenient for defining the flow before all patterns have been added to the song file.
|
206
|
-
raise ParseError, "Song flow includes non-existent pattern: #{pattern_name}
|
203
|
+
raise ParseError, "Song flow includes non-existent pattern: '#{pattern_name}'"
|
207
204
|
end
|
208
205
|
end
|
209
206
|
|
210
207
|
multiples.times { flow << pattern_name_sym }
|
211
|
-
|
208
|
+
end
|
209
|
+
|
212
210
|
song.flow = flow
|
213
211
|
end
|
214
212
|
|
215
213
|
|
216
214
|
# Converts all hash keys to be lowercase
|
217
|
-
def downcase_hash_keys(hash)
|
218
|
-
|
215
|
+
def self.downcase_hash_keys(hash)
|
216
|
+
hash.inject({}) do |new_hash, pair|
|
219
217
|
new_hash[pair.first.downcase] = pair.last
|
220
218
|
new_hash
|
221
219
|
end
|
data/lib/beats/track.rb
CHANGED
@@ -5,38 +5,35 @@ module Beats
|
|
5
5
|
# This object is like sheet music; the AudioEngine is responsible creating actual
|
6
6
|
# audio data for a Track (with the help of a Kit).
|
7
7
|
class Track
|
8
|
-
class InvalidRhythmError <
|
8
|
+
class InvalidRhythmError < ArgumentError; end
|
9
9
|
|
10
10
|
REST = "."
|
11
11
|
BEAT = "X"
|
12
12
|
BARLINE = "|"
|
13
13
|
SPACE = " "
|
14
|
-
DISALLOWED_CHARACTERS = /[^X
|
14
|
+
DISALLOWED_CHARACTERS = /[^X\.| ]/ # I.e., anything not an 'X', '.', '|', or ' '
|
15
15
|
|
16
16
|
def initialize(name, rhythm)
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
unless name.is_a?(String)
|
18
|
+
raise ArgumentError, "Track name '#{name.inspect}' is invalid, must be a String"
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@trigger_step_lengths = calculate_trigger_step_lengths
|
25
|
-
end
|
21
|
+
unless rhythm.is_a?(String) && rhythm.match(DISALLOWED_CHARACTERS) == nil
|
22
|
+
raise InvalidRhythmError, "Track '#{name}' has an invalid rhythm: '#{rhythm.inspect}'. Can only contain '#{BEAT}', '#{REST}', '#{BARLINE}', or ' '"
|
23
|
+
end
|
26
24
|
|
27
|
-
|
28
|
-
@rhythm.
|
25
|
+
@name = name.dup.freeze
|
26
|
+
@rhythm = rhythm.delete(BARLINE + SPACE).freeze
|
27
|
+
|
28
|
+
@step_count = @rhythm.length
|
29
|
+
@trigger_step_lengths = calculate_trigger_step_lengths.freeze
|
29
30
|
end
|
30
31
|
|
31
|
-
attr_reader :name, :rhythm, :trigger_step_lengths
|
32
|
+
attr_reader :name, :rhythm, :step_count, :trigger_step_lengths
|
32
33
|
|
33
34
|
private
|
34
35
|
|
35
36
|
def calculate_trigger_step_lengths
|
36
|
-
if @rhythm.match(DISALLOWED_CHARACTERS)
|
37
|
-
raise InvalidRhythmError, "Track #{@name} has an invalid rhythm: '#{rhythm}'. Can only contain '#{BEAT}', '#{REST}', '#{BARLINE}', or ' '"
|
38
|
-
end
|
39
|
-
|
40
37
|
trigger_step_lengths = @rhythm.scan(/X?\.*/)[0..-2].map(&:length)
|
41
38
|
trigger_step_lengths.unshift(0) unless @rhythm.start_with?(REST)
|
42
39
|
|
@@ -1,19 +1,21 @@
|
|
1
1
|
module Beats
|
2
2
|
module Transforms
|
3
3
|
class SongSwinger
|
4
|
-
class InvalidSwingRateError <
|
4
|
+
class InvalidSwingRateError < ArgumentError; end
|
5
5
|
|
6
6
|
def self.transform(song, swing_rate)
|
7
7
|
validate_swing_rate(swing_rate)
|
8
8
|
|
9
|
-
song.patterns.
|
10
|
-
pattern.tracks.
|
9
|
+
song.patterns.each do |pattern_name, pattern|
|
10
|
+
swung_tracks = pattern.tracks.map do |track_name, track|
|
11
11
|
if swing_rate == 8
|
12
|
-
track.
|
12
|
+
Track.new(track.name, swing_8(track.rhythm))
|
13
13
|
elsif swing_rate == 16
|
14
|
-
track.
|
14
|
+
Track.new(track.name, swing_16(track.rhythm))
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
song.patterns[pattern_name] = Pattern.new(pattern_name, swung_tracks)
|
17
19
|
end
|
18
20
|
|
19
21
|
song.tempo *= 1.5
|
@@ -3,7 +3,7 @@ module WaveFile
|
|
3
3
|
# If the Buffer is written again, it will write the version from cache instead of re-doing
|
4
4
|
# a String.pack() call.
|
5
5
|
class CachingWriter < Writer
|
6
|
-
def initialize(
|
6
|
+
def initialize(io_or_file_name, format)
|
7
7
|
super
|
8
8
|
|
9
9
|
@buffer_cache = {}
|
@@ -25,7 +25,7 @@ module WaveFile
|
|
25
25
|
data = samples.pack(@pack_code)
|
26
26
|
end
|
27
27
|
|
28
|
-
packed_buffer_data = { :
|
28
|
+
packed_buffer_data = { data: data, sample_count: samples.length }
|
29
29
|
@buffer_cache[key] = packed_buffer_data
|
30
30
|
end
|
31
31
|
|
@@ -27,12 +27,11 @@ class AudioEngineTest < Minitest::Test
|
|
27
27
|
def load_fixtures
|
28
28
|
test_engines = {}
|
29
29
|
base_path = File.dirname(__FILE__) + "/.."
|
30
|
-
song_parser = SongParser.new
|
31
30
|
|
32
31
|
test_engines[:blank] = AudioEngine.new(Song.new, KitBuilder.new(base_path).build_kit)
|
33
32
|
|
34
33
|
FIXTURES.each do |fixture_name|
|
35
|
-
song, kit =
|
34
|
+
song, kit = SongParser.parse(base_path, File.read("test/fixtures/valid/#{fixture_name}.txt"))
|
36
35
|
test_engines[fixture_name] = AudioEngine.new(song, kit)
|
37
36
|
end
|
38
37
|
|
@@ -92,24 +91,24 @@ class AudioEngineTest < Minitest::Test
|
|
92
91
|
# 1.) Tick sample length is equal to the length of the sound sample data.
|
93
92
|
# When this is the case, overflow should never occur.
|
94
93
|
# In practice, this will probably not occur often, but these tests act as a form of sanity check.
|
95
|
-
helper_generate_track_sample_data
|
96
|
-
helper_generate_track_sample_data
|
97
|
-
helper_generate_track_sample_data
|
98
|
-
helper_generate_track_sample_data
|
99
|
-
helper_generate_track_sample_data
|
100
|
-
helper_generate_track_sample_data
|
101
|
-
helper_generate_track_sample_data
|
94
|
+
helper_generate_track_sample_data(kit, "", 4, [])
|
95
|
+
helper_generate_track_sample_data(kit, "X", 4, s)
|
96
|
+
helper_generate_track_sample_data(kit, "X.", 4, s + te)
|
97
|
+
helper_generate_track_sample_data(kit, ".X", 4, te + s)
|
98
|
+
helper_generate_track_sample_data(kit, "...X.", 4, (te * 3) + s + te)
|
99
|
+
helper_generate_track_sample_data(kit, ".X.XX.", 4, te + s + te + s + s + te)
|
100
|
+
helper_generate_track_sample_data(kit, "...", 4, te * 3)
|
102
101
|
|
103
102
|
# 2A.) Tick sample length is longer than the sound sample data. This is similar to (1), except that there should
|
104
103
|
# be some extra silence after the end of each trigger.
|
105
104
|
# Like (1), overflow should never occur.
|
106
|
-
helper_generate_track_sample_data
|
107
|
-
helper_generate_track_sample_data
|
108
|
-
helper_generate_track_sample_data
|
109
|
-
helper_generate_track_sample_data
|
110
|
-
helper_generate_track_sample_data
|
111
|
-
helper_generate_track_sample_data
|
112
|
-
helper_generate_track_sample_data
|
105
|
+
helper_generate_track_sample_data(kit, "", 6, [])
|
106
|
+
helper_generate_track_sample_data(kit, "X", 6, sl)
|
107
|
+
helper_generate_track_sample_data(kit, "X.", 6, sl + tl)
|
108
|
+
helper_generate_track_sample_data(kit, ".X", 6, tl + sl)
|
109
|
+
helper_generate_track_sample_data(kit, "...X.", 6, (tl * 3) + sl + tl)
|
110
|
+
helper_generate_track_sample_data(kit, ".X.XX.", 6, tl + sl + tl + sl + sl + tl)
|
111
|
+
helper_generate_track_sample_data(kit, "...", 6, (te + ts) * 3)
|
113
112
|
|
114
113
|
# 2B.) Tick sample length is longer than the sound sample data, but not by an integer amount.
|
115
114
|
#
|
@@ -117,22 +116,22 @@ class AudioEngineTest < Minitest::Test
|
|
117
116
|
# Tick: 1, 2, 3, 4, 5, 6
|
118
117
|
# Raw: 0.0, 5.83, 11.66, 17.49, 23.32, 29.15, 34.98
|
119
118
|
# Quantized: 0, 5, 11, 17, 23, 29, 34
|
120
|
-
helper_generate_track_sample_data
|
121
|
-
helper_generate_track_sample_data
|
122
|
-
helper_generate_track_sample_data
|
123
|
-
helper_generate_track_sample_data
|
124
|
-
helper_generate_track_sample_data
|
125
|
-
helper_generate_track_sample_data
|
126
|
-
helper_generate_track_sample_data
|
119
|
+
helper_generate_track_sample_data(kit, "", 5.83, [])
|
120
|
+
helper_generate_track_sample_data(kit, "X", 5.83, sl[0..4])
|
121
|
+
helper_generate_track_sample_data(kit, "X.", 5.83, sl[0..4] + tl)
|
122
|
+
helper_generate_track_sample_data(kit, ".X", 5.83, tl[0..4] + sl)
|
123
|
+
helper_generate_track_sample_data(kit, "...X.", 5.83, (z * 17) + sl + tl)
|
124
|
+
helper_generate_track_sample_data(kit, ".X.XX.", 5.83, tl[0..4] + sl + tl + sl + sl + tl[0..4])
|
125
|
+
helper_generate_track_sample_data(kit, "...", 5.83, z * 17)
|
127
126
|
|
128
127
|
# 3A.) Tick sample length is shorter than the sound sample data. Overflow will now occur!
|
129
|
-
helper_generate_track_sample_data
|
130
|
-
helper_generate_track_sample_data
|
131
|
-
helper_generate_track_sample_data
|
132
|
-
helper_generate_track_sample_data
|
133
|
-
helper_generate_track_sample_data
|
134
|
-
helper_generate_track_sample_data
|
135
|
-
helper_generate_track_sample_data
|
128
|
+
helper_generate_track_sample_data(kit, "", 2, [], [])
|
129
|
+
helper_generate_track_sample_data(kit, "X", 2, ss, so)
|
130
|
+
helper_generate_track_sample_data(kit, "X.", 2, s, [])
|
131
|
+
helper_generate_track_sample_data(kit, ".X", 2, ts + ss, so)
|
132
|
+
helper_generate_track_sample_data(kit, "...X.", 2, (ts * 3) + s, [])
|
133
|
+
helper_generate_track_sample_data(kit, ".X.XX.", 2, ts + s + ss + s, [])
|
134
|
+
helper_generate_track_sample_data(kit, "...", 2, z * 6, [])
|
136
135
|
|
137
136
|
# 3B.) Tick sample length is shorter than sound sample data, such that a beat other than the final one
|
138
137
|
# would extend past the end of the rhythm if not cut off. Make sure that the sample data array doesn't
|
@@ -145,13 +144,13 @@ class AudioEngineTest < Minitest::Test
|
|
145
144
|
# Tick: 1, 2, 3, 4, 5, 6
|
146
145
|
# Raw: 0.0, 1.83, 3.66, 5.49, 7.32, 9.15, 10.98
|
147
146
|
# Quantized: 0, 1, 3, 5, 7, 9, 10
|
148
|
-
helper_generate_track_sample_data
|
149
|
-
helper_generate_track_sample_data
|
150
|
-
helper_generate_track_sample_data
|
151
|
-
helper_generate_track_sample_data
|
152
|
-
helper_generate_track_sample_data
|
153
|
-
helper_generate_track_sample_data
|
154
|
-
helper_generate_track_sample_data
|
147
|
+
helper_generate_track_sample_data(kit, "", 1.83, [])
|
148
|
+
helper_generate_track_sample_data(kit, "X", 1.83, s[0..0], s[1..3])
|
149
|
+
helper_generate_track_sample_data(kit, "X.", 1.83, s[0..2], s[3..3])
|
150
|
+
helper_generate_track_sample_data(kit, ".X", 1.83, z + s[0..1], s[2..3])
|
151
|
+
helper_generate_track_sample_data(kit, "...X.", 1.83, (z * 5) + s, [])
|
152
|
+
helper_generate_track_sample_data(kit, ".X.XX.", 1.83, z + s + ss + s[0..2], s[3..3])
|
153
|
+
helper_generate_track_sample_data(kit, "...", 1.83, z * 5, [])
|
155
154
|
end
|
156
155
|
end
|
157
156
|
|
@@ -168,15 +167,19 @@ class AudioEngineTest < Minitest::Test
|
|
168
167
|
end
|
169
168
|
|
170
169
|
def test_composite_pattern_tracks
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
170
|
+
no_overflow_tracks = [
|
171
|
+
Track.new("S", "X..."),
|
172
|
+
Track.new("SO", "X.X."),
|
173
|
+
Track.new("S", "X.XX"),
|
174
|
+
]
|
175
|
+
no_overflow_pattern = Pattern.new("no_overflow", no_overflow_tracks)
|
176
|
+
|
177
|
+
overflow_tracks = [
|
178
|
+
Track.new("S", "X..X"),
|
179
|
+
Track.new("SO", "XX.X"),
|
180
|
+
Track.new("SL", ".X.X"),
|
181
|
+
]
|
182
|
+
overflow_pattern = Pattern.new("overflow", overflow_tracks)
|
180
183
|
|
181
184
|
|
182
185
|
# Simple case, no overflow (stereo)
|