beats 2.1.0 → 2.1.1
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.
- 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)
|