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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -2
  3. data/README.markdown +17 -80
  4. data/bin/beats +2 -7
  5. data/lib/beats.rb +12 -6
  6. data/lib/beats/{audioengine.rb → audio_engine.rb} +7 -8
  7. data/lib/beats/{audioutils.rb → audio_utils.rb} +35 -17
  8. data/lib/beats/{beatsrunner.rb → beats_runner.rb} +2 -2
  9. data/lib/beats/kit.rb +6 -5
  10. data/lib/beats/kit_builder.rb +12 -8
  11. data/lib/beats/pattern.rb +7 -16
  12. data/lib/beats/song.rb +7 -8
  13. data/lib/beats/{songoptimizer.rb → song_optimizer.rb} +11 -8
  14. data/lib/beats/{songparser.rb → song_parser.rb} +46 -48
  15. data/lib/beats/track.rb +14 -17
  16. data/lib/beats/transforms/song_swinger.rb +7 -5
  17. data/lib/wavefile/{cachingwriter.rb → caching_writer.rb} +2 -2
  18. data/test/{audioengine_test.rb → audio_engine_test.rb} +49 -46
  19. data/test/audio_utils_test.rb +86 -0
  20. data/test/{cachingwriter_test.rb → caching_writer_test.rb} +0 -0
  21. data/test/fixtures/invalid/sound_in_kit_wrong_format.txt +1 -1
  22. data/test/fixtures/invalid/sound_in_track_wrong_format.txt +1 -1
  23. data/test/fixtures/valid/empty_kit.txt +14 -0
  24. data/test/fixtures/valid/example_song_header_different_capitalization.txt +21 -0
  25. data/test/fixtures/valid/multiple_patterns_same_name.txt +31 -0
  26. data/test/fixtures/valid/multiple_song_header_sections.txt +29 -0
  27. data/test/includes.rb +0 -9
  28. data/test/kit_builder_test.rb +20 -0
  29. data/test/kit_test.rb +7 -0
  30. data/test/pattern_test.rb +86 -74
  31. data/test/{songoptimizer_test.rb → song_optimizer_test.rb} +5 -8
  32. data/test/{songparser_test.rb → song_parser_test.rb} +109 -13
  33. data/test/song_swinger_test.rb +6 -4
  34. data/test/song_test.rb +32 -14
  35. data/test/sounds/agogo_high_stereo_16.wav +0 -0
  36. data/test/sounds/agogo_low_stereo_16.wav +0 -0
  37. data/test/sounds/bass2_stereo_16.wav +0 -0
  38. data/test/sounds/bass_stereo_16.wav +0 -0
  39. data/test/sounds/clave_high_stereo_16.wav +0 -0
  40. data/test/sounds/clave_low_stereo_16.wav +0 -0
  41. data/test/sounds/conga_high_stereo_16.wav +0 -0
  42. data/test/sounds/conga_low_stereo_16.wav +0 -0
  43. data/test/sounds/cowbell_high_stereo_16.wav +0 -0
  44. data/test/sounds/cowbell_low_stereo_16.wav +0 -0
  45. data/test/sounds/hh_closed_stereo_16.wav +0 -0
  46. data/test/sounds/hh_open_stereo_16.wav +0 -0
  47. data/test/sounds/ride_stereo_16.wav +0 -0
  48. data/test/sounds/rim_stereo_16.wav +0 -0
  49. data/test/sounds/snare2_stereo_16.wav +0 -0
  50. data/test/sounds/snare_stereo_16.wav +0 -0
  51. data/test/sounds/tom1_stereo_16.wav +0 -0
  52. data/test/sounds/tom2_stereo_16.wav +0 -0
  53. data/test/sounds/tom3_stereo_16.wav +0 -0
  54. data/test/sounds/tom4_stereo_16.wav +0 -0
  55. data/test/track_test.rb +51 -5
  56. metadata +184 -176
  57. data/test/audioutils_test.rb +0 -46
@@ -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 |name, original_pattern|
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
- original_track = original_pattern.tracks[track_name]
83
- new_pattern.track(original_track.name, original_track.rhythm)
80
+ new_track = original_pattern.tracks[track_name]
84
81
  else
85
- new_pattern.track(track_name, "." * original_pattern.step_count)
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
- blank_track_pattern = '.' * max_pattern_length
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
- new_pattern = optimized_song.pattern("#{pattern.name}_#{step_index}".to_sym)
72
- optimized_flow[pattern.name] << new_pattern.name
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
- sub_track_pattern = track.rhythm[step_index...(step_index + max_pattern_length)]
75
+ sub_track_rhythm = track.rhythm[step_index...(step_index + max_pattern_length)]
75
76
 
76
- if sub_track_pattern != blank_track_pattern
77
- new_pattern.track(track.name, sub_track_pattern)
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 new_pattern.tracks.empty?
85
- new_pattern.track(Kit::PLACEHOLDER_TRACK_NAME, blank_track_pattern)
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 < RuntimeError; end
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
- raw_song_components = {}
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
- unless raw_song_components[:full_definition]["song"].nil?
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
- raw_song_components[:flow] = raw_song_components[:header]["flow"]
111
-
112
- raw_song_components[:swing] = raw_song_components[:header]["swing"]
113
- raw_song_components[:patterns] = raw_song_components[:full_definition].reject {|k, v| k == "song"}
114
-
115
- return raw_song_components
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
- # TODO: Raise error is same name is defined more than once in the Kit
122
- unless raw_kit.nil?
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
- new_pattern = song.pattern(pattern_name.to_sym)
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}' has an empty composite pattern (i.e. \"[]\"), which is not valid."
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
- new_pattern.track track_name, rhythm
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{|pattern_item|
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
- return hash.inject({}) do |new_hash, pair|
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
@@ -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 < RuntimeError; end
8
+ class InvalidRhythmError < ArgumentError; end
9
9
 
10
10
  REST = "."
11
11
  BEAT = "X"
12
12
  BARLINE = "|"
13
13
  SPACE = " "
14
- DISALLOWED_CHARACTERS = /[^X\.]/ # I.e., anything not an 'X' or a '.'
14
+ DISALLOWED_CHARACTERS = /[^X\.| ]/ # I.e., anything not an 'X', '.', '|', or ' '
15
15
 
16
16
  def initialize(name, rhythm)
17
- @name = name
18
- self.rhythm = rhythm
19
- end
17
+ unless name.is_a?(String)
18
+ raise ArgumentError, "Track name '#{name.inspect}' is invalid, must be a String"
19
+ end
20
20
 
21
- def rhythm=(rhythm)
22
- @rhythm = rhythm.delete(BARLINE)
23
- @rhythm = @rhythm.delete(SPACE)
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
- def step_count
28
- @rhythm.length
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 < RuntimeError; end
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.values.each do |pattern|
10
- pattern.tracks.values.each do |track|
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.rhythm = swing_8(track.rhythm)
12
+ Track.new(track.name, swing_8(track.rhythm))
13
13
  elsif swing_rate == 16
14
- track.rhythm = swing_16(track.rhythm)
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(file_name, format)
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 = { :data => data, :sample_count => samples.length }
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 = song_parser.parse(base_path, File.read("test/fixtures/valid/#{fixture_name}.txt"))
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 kit, "", 4, []
96
- helper_generate_track_sample_data kit, "X", 4, s
97
- helper_generate_track_sample_data kit, "X.", 4, s + te
98
- helper_generate_track_sample_data kit, ".X", 4, te + s
99
- helper_generate_track_sample_data kit, "...X.", 4, (te * 3) + s + te
100
- helper_generate_track_sample_data kit, ".X.XX.", 4, te + s + te + s + s + te
101
- helper_generate_track_sample_data kit, "...", 4, te * 3
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 kit, "", 6, []
107
- helper_generate_track_sample_data kit, "X", 6, sl
108
- helper_generate_track_sample_data kit, "X.", 6, sl + tl
109
- helper_generate_track_sample_data kit, ".X", 6, tl + sl
110
- helper_generate_track_sample_data kit, "...X.", 6, (tl * 3) + sl + tl
111
- helper_generate_track_sample_data kit, ".X.XX.", 6, tl + sl + tl + sl + sl + tl
112
- helper_generate_track_sample_data kit, "...", 6, (te + ts) * 3
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 kit, "", 5.83, []
121
- helper_generate_track_sample_data kit, "X", 5.83, sl[0..4]
122
- helper_generate_track_sample_data kit, "X.", 5.83, sl[0..4] + tl
123
- helper_generate_track_sample_data kit, ".X", 5.83, tl[0..4] + sl
124
- helper_generate_track_sample_data kit, "...X.", 5.83, (z * 17) + sl + tl
125
- helper_generate_track_sample_data kit, ".X.XX.", 5.83, tl[0..4] + sl + tl + sl + sl + tl[0..4]
126
- helper_generate_track_sample_data kit, "...", 5.83, z * 17
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 kit, "", 2, [], []
130
- helper_generate_track_sample_data kit, "X", 2, ss, so
131
- helper_generate_track_sample_data kit, "X.", 2, s, []
132
- helper_generate_track_sample_data kit, ".X", 2, ts + ss, so
133
- helper_generate_track_sample_data kit, "...X.", 2, (ts * 3) + s, []
134
- helper_generate_track_sample_data kit, ".X.XX.", 2, ts + s + ss + s, []
135
- helper_generate_track_sample_data kit, "...", 2, z * 6, []
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 kit, "", 1.83, []
149
- helper_generate_track_sample_data kit, "X", 1.83, s[0..0], s[1..3]
150
- helper_generate_track_sample_data kit, "X.", 1.83, s[0..2], s[3..3]
151
- helper_generate_track_sample_data kit, ".X", 1.83, z + s[0..1], s[2..3]
152
- helper_generate_track_sample_data kit, "...X.", 1.83, (z * 5) + s, []
153
- helper_generate_track_sample_data kit, ".X.XX.", 1.83, z + s + ss + s[0..2], s[3..3]
154
- helper_generate_track_sample_data kit, "...", 1.83, z * 5, []
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
- no_overflow_pattern = Pattern.new("no_overflow")
172
- no_overflow_pattern.track "S", "X..."
173
- no_overflow_pattern.track "SO", "X.X."
174
- no_overflow_pattern.track "S", "X.XX"
175
-
176
- overflow_pattern = Pattern.new("overflow")
177
- overflow_pattern.track "S", "X..X"
178
- overflow_pattern.track "SO", "XX.X"
179
- overflow_pattern.track "SL", ".X.X"
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)