beats 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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)