beats 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/LICENSE +1 -1
  2. data/README.markdown +28 -10
  3. data/bin/beats +9 -7
  4. data/lib/audioengine.rb +172 -0
  5. data/lib/audioutils.rb +73 -0
  6. data/lib/beats.rb +14 -15
  7. data/lib/beatswavefile.rb +17 -37
  8. data/lib/kit.rb +148 -71
  9. data/lib/pattern.rb +20 -117
  10. data/lib/patternexpander.rb +111 -0
  11. data/lib/song.rb +78 -132
  12. data/lib/songoptimizer.rb +29 -33
  13. data/lib/songparser.rb +70 -45
  14. data/lib/track.rb +11 -82
  15. data/test/audioengine_test.rb +261 -0
  16. data/test/audioutils_test.rb +45 -0
  17. data/test/fixtures/expected_output/example_split_mono_16-hh_closed.wav +0 -0
  18. data/test/{examples/split-agogo_high.wav → fixtures/expected_output/example_split_mono_16-hh_closed2.wav} +0 -0
  19. data/test/fixtures/expected_output/example_split_mono_8-hh_closed.wav +0 -0
  20. data/test/{examples/split-tom4.wav → fixtures/expected_output/example_split_mono_8-hh_closed2.wav} +0 -0
  21. data/test/fixtures/expected_output/example_split_stereo_16-hh_closed.wav +0 -0
  22. data/test/fixtures/expected_output/example_split_stereo_16-hh_closed2.wav +0 -0
  23. data/test/fixtures/expected_output/example_split_stereo_8-hh_closed.wav +0 -0
  24. data/test/fixtures/expected_output/example_split_stereo_8-hh_closed2.wav +0 -0
  25. data/test/fixtures/invalid/{bad_structure.txt → bad_flow.txt} +2 -2
  26. data/test/fixtures/invalid/bad_repeat_count.txt +1 -1
  27. data/test/fixtures/invalid/bad_rhythm.txt +1 -1
  28. data/test/fixtures/invalid/bad_tempo.txt +1 -1
  29. data/test/fixtures/invalid/{no_structure.txt → no_flow.txt} +1 -1
  30. data/test/fixtures/invalid/pattern_with_no_tracks.txt +1 -1
  31. data/test/fixtures/invalid/sound_in_kit_not_found.txt +1 -1
  32. data/test/fixtures/invalid/sound_in_kit_wrong_format.txt +10 -0
  33. data/test/fixtures/invalid/sound_in_track_not_found.txt +1 -1
  34. data/test/fixtures/invalid/sound_in_track_wrong_format.txt +8 -0
  35. data/test/fixtures/invalid/template.txt +1 -1
  36. data/test/fixtures/valid/example_mono_16.txt +5 -3
  37. data/test/fixtures/valid/example_mono_8.txt +5 -3
  38. data/test/fixtures/valid/example_no_kit.txt +1 -1
  39. data/test/fixtures/valid/example_stereo_16.txt +7 -4
  40. data/test/fixtures/valid/example_stereo_8.txt +5 -3
  41. data/test/fixtures/valid/example_with_empty_track.txt +1 -1
  42. data/test/fixtures/valid/example_with_kit.txt +1 -1
  43. data/test/fixtures/valid/multiple_tracks_same_sound.txt +33 -0
  44. data/test/fixtures/valid/no_tempo.txt +1 -1
  45. data/test/fixtures/valid/optimize_pattern_collision.txt +28 -0
  46. data/test/fixtures/valid/pattern_with_overflow.txt +1 -1
  47. data/test/fixtures/valid/repeats_not_specified.txt +2 -2
  48. data/test/fixtures/valid/with_structure.txt +10 -0
  49. data/test/fixtures/yaml/song_yaml.txt +5 -5
  50. data/test/includes.rb +4 -2
  51. data/test/integration.rb +3 -3
  52. data/test/kit_test.rb +136 -109
  53. data/test/pattern_test.rb +31 -131
  54. data/test/patternexpander_test.rb +142 -0
  55. data/test/song_test.rb +104 -102
  56. data/test/songoptimizer_test.rb +52 -38
  57. data/test/songparser_test.rb +79 -46
  58. data/test/sounds/composite_snare_mono_8_tom3_mono_16_mono_16.wav +0 -0
  59. data/test/sounds/composite_snare_mono_8_tom3_mono_8_mono_16.wav +0 -0
  60. data/test/sounds/composite_snare_stereo_16_tom3_mono_16_stereo_16.wav +0 -0
  61. data/test/sounds/composite_snare_stereo_8_tom3_mono_16_stereo_16.wav +0 -0
  62. data/test/track_test.rb +30 -185
  63. metadata +56 -24
  64. data/lib/songsplitter.rb +0 -38
  65. data/test/examples/combined.wav +0 -0
  66. data/test/examples/split-bass.wav +0 -0
  67. data/test/examples/split-hh_closed.wav +0 -0
  68. data/test/examples/split-snare.wav +0 -0
  69. data/test/examples/split-tom2.wav +0 -0
@@ -3,24 +3,23 @@ class InvalidRhythmError < RuntimeError; end
3
3
  class Track
4
4
  REST = "."
5
5
  BEAT = "X"
6
+ BARLINE = "|"
6
7
 
7
- def initialize(name, wave_data, rhythm)
8
+ def initialize(name, rhythm)
8
9
  # TODO: Add validation for input parameters
9
-
10
- @wave_data = wave_data
11
10
  @name = name
12
- @sample_data = nil
13
- @overflow = nil
14
11
  self.rhythm = rhythm
15
12
  end
16
13
 
14
+ # TODO: What to have this invoked when setting like this?
15
+ # track.rhythm[x..y] = whatever
17
16
  def rhythm=(rhythm)
18
- @rhythm = rhythm
17
+ @rhythm = rhythm.delete(BARLINE)
19
18
  beats = []
20
19
 
21
20
  beat_length = 0
22
21
  #rhythm.each_char{|ch|
23
- rhythm.each_byte do |ch|
22
+ @rhythm.each_byte do |ch|
24
23
  ch = ch.chr
25
24
  if ch == BEAT
26
25
  beats << beat_length
@@ -39,82 +38,12 @@ class Track
39
38
  beats = [0]
40
39
  end
41
40
  @beats = beats
42
-
43
- # Remove any cached sample data
44
- @sample_data = nil
45
- @overflow = nil
46
- end
47
-
48
- def intro_sample_length(tick_sample_length)
49
- return @beats[0] * tick_sample_length.floor
50
41
  end
51
42
 
52
- def sample_length(tick_sample_length)
53
- total_ticks = @beats.inject(0) {|sum, n| sum + n}
54
- return (total_ticks * tick_sample_length).floor
55
- end
56
-
57
- def sample_length_with_overflow(tick_sample_length)
58
- temp_sample_length = sample_length(tick_sample_length)
59
-
60
- unless @beats == [0]
61
- beat_sample_length = @beats.last * tick_sample_length
62
- if(@wave_data.length > beat_sample_length)
63
- temp_sample_length += @wave_data.length - beat_sample_length.floor
64
- end
65
- end
66
-
67
- return temp_sample_length.floor
68
- end
69
-
70
- def tick_count
43
+ def step_count
71
44
  return @rhythm.length
72
45
  end
73
-
74
- def sample_data(tick_sample_length)
75
- actual_sample_length = sample_length(tick_sample_length)
76
- full_sample_length = sample_length_with_overflow(tick_sample_length)
77
-
78
- if @sample_data == nil
79
- fill_value = (@wave_data.first.class == Array) ? [0, 0] : 0
80
- output_data = [].fill(fill_value, 0, full_sample_length)
81
-
82
- if full_sample_length > 0
83
- remainder = 0.0
84
- offset = @beats[0] * tick_sample_length
85
- remainder += (@beats[0] * tick_sample_length) - (@beats[0] * tick_sample_length).floor
86
-
87
- @beats[1...(@beats.length)].each do |beat_length|
88
- beat_sample_length = beat_length * tick_sample_length
89
-
90
- remainder += beat_sample_length - beat_sample_length.floor
91
- if remainder >= 1.0
92
- beat_sample_length += 1
93
- remainder -= 1.0
94
- end
95
-
96
- output_data[offset...(offset + wave_data.length)] = wave_data
97
- offset += beat_sample_length.floor
98
- end
99
-
100
- if full_sample_length > actual_sample_length
101
- @sample_data = output_data[0...offset]
102
- @overflow = output_data[actual_sample_length...full_sample_length]
103
- else
104
- @sample_data = output_data
105
- @overflow = []
106
- end
107
- else
108
- @sample_data = []
109
- @overflow = []
110
- end
111
- end
112
-
113
- primary_sample_data = @sample_data.dup
114
-
115
- return {:primary => primary_sample_data, :overflow => @overflow}
116
- end
117
-
118
- attr_accessor :name, :wave_data
119
- attr_reader :rhythm
120
- end
46
+
47
+ attr_accessor :name
48
+ attr_reader :rhythm, :beats
49
+ end
@@ -0,0 +1,261 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'test/includes'
4
+
5
+ # Make private methods public for testing
6
+ class MockAudioEngine < AudioEngine
7
+ def generate_track_sample_data(track, sound)
8
+ super
9
+ end
10
+
11
+ def composite_pattern_tracks(pattern)
12
+ super
13
+ end
14
+
15
+ attr_accessor :step_sample_length
16
+ end
17
+
18
+ # Allow setting sample data directly, instead of loading from a file
19
+ class MockKit < Kit
20
+ attr_accessor :sound_bank, :num_channels
21
+ end
22
+
23
+ class AudioEngineTest < Test::Unit::TestCase
24
+ FIXTURES = [:repeats_not_specified,
25
+ :pattern_with_overflow,
26
+ :example_no_kit,
27
+ :example_with_kit]
28
+
29
+ def load_fixtures
30
+ test_engines = {}
31
+ base_path = File.dirname(__FILE__) + "/.."
32
+ song_parser = SongParser.new()
33
+
34
+ test_engines[:blank] = AudioEngine.new(Song.new(), Kit.new(base_path, {}))
35
+
36
+ FIXTURES.each do |fixture_name|
37
+ song, kit = song_parser.parse(base_path, File.read("test/fixtures/valid/#{fixture_name}.txt"))
38
+ test_engines[fixture_name] = AudioEngine.new(song, kit)
39
+ end
40
+
41
+ return test_engines
42
+ end
43
+
44
+ def test_initialize
45
+ test_engines = load_fixtures()
46
+
47
+ assert_equal(5512.5, test_engines[:blank].step_sample_length)
48
+ assert_equal(6615.0, test_engines[:repeats_not_specified].step_sample_length)
49
+ end
50
+
51
+
52
+ # S Sample data for a sound. Unrealistically short for clarity.
53
+ # SL Sound when step sample length is longer than full sound length
54
+ # SS Sound when step sample length is less than full sound length
55
+ # SO Sound overflow when step sample length is less than full sound length
56
+ # TE A step with no sound, with length equal to S
57
+ # TL A step with no sound, longer than full sound length
58
+ # TS A step with no sound, shorter than full sound length
59
+ # Z A zero sample
60
+
61
+ MONO_KIT = MockKit.new(".", {})
62
+ MONO_KIT.sound_bank = { "S" => [-100, 200, 300, -400],
63
+ "SL" => [-100, 200, 300, -400, 0, 0],
64
+ "SS" => [-100, 200],
65
+ "SO" => [300, -400],
66
+ "TE" => [0, 0, 0, 0],
67
+ "TL" => [0, 0, 0, 0, 0, 0],
68
+ "TS" => [0, 0],
69
+ "Z" => [0] }
70
+ MONO_KIT.num_channels = 1
71
+
72
+ STEREO_KIT = MockKit.new(".", {})
73
+ STEREO_KIT.sound_bank = { "S" => [[-100, 800], [200, -700], [300, -600], [-400, 400]],
74
+ "SL" => [[-100, 800], [200, -700], [300, -600], [-400, 400], [0, 0], [0, 0]],
75
+ "SS" => [[-100, 800], [200, -700]],
76
+ "SO" => [[300, -600], [-400, 400]],
77
+ "TE" => [[0, 0], [0, 0], [0, 0], [0, 0]],
78
+ "TL" => [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
79
+ "TS" => [[0, 0], [0, 0]],
80
+ "Z" => [[0, 0]] }
81
+ STEREO_KIT.num_channels = 2
82
+
83
+
84
+ # These tests use unrealistically short sounds and step sample lengths, to make tests easier to work with.
85
+ def test_generate_track_sample_data
86
+ [MONO_KIT, STEREO_KIT].each do |kit|
87
+ s = kit.get_sample_data("S")
88
+ sl = kit.get_sample_data("SL")
89
+ ss = kit.get_sample_data("SS")
90
+ so = kit.get_sample_data("SO")
91
+ te = kit.get_sample_data("TE")
92
+ tl = kit.get_sample_data("TL")
93
+ ts = kit.get_sample_data("TS")
94
+ z = kit.get_sample_data("Z")
95
+
96
+ # 1.) Tick sample length is equal to the length of the sound sample data.
97
+ # When this is the case, overflow should never occur.
98
+ # In practice, this will probably not occur often, but these tests act as a form of sanity check.
99
+ helper_generate_track_sample_data kit, "", 4, []
100
+ helper_generate_track_sample_data kit, "X", 4, s
101
+ helper_generate_track_sample_data kit, "X.", 4, s + te
102
+ helper_generate_track_sample_data kit, ".X", 4, te + s
103
+ helper_generate_track_sample_data kit, "...X.", 4, (te * 3) + s + te
104
+ helper_generate_track_sample_data kit, ".X.XX.", 4, te + s + te + s + s + te
105
+ helper_generate_track_sample_data kit, "...", 4, te * 3
106
+
107
+ # 2A.) Tick sample length is longer than the sound sample data. This is similar to (1), except that there should
108
+ # be some extra silence after the end of each trigger.
109
+ # Like (1), overflow should never occur.
110
+ helper_generate_track_sample_data kit, "", 6, []
111
+ helper_generate_track_sample_data kit, "X", 6, sl
112
+ helper_generate_track_sample_data kit, "X.", 6, sl + tl
113
+ helper_generate_track_sample_data kit, ".X", 6, tl + sl
114
+ helper_generate_track_sample_data kit, "...X.", 6, (tl * 3) + sl + tl
115
+ helper_generate_track_sample_data kit, ".X.XX.", 6, tl + sl + tl + sl + sl + tl
116
+ helper_generate_track_sample_data kit, "...", 6, (te + ts) * 3
117
+
118
+ # 2B.) Tick sample length is longer than the sound sample data, but not by an integer amount.
119
+ #
120
+ # Each step of 5.83 samples should end on the following boundaries:
121
+ # Tick: 1, 2, 3, 4, 5, 6
122
+ # Raw: 0.0, 5.83, 11.66, 17.49, 23.32, 29.15, 34.98
123
+ # Quantized: 0, 5, 11, 17, 23, 29, 34
124
+ helper_generate_track_sample_data kit, "", 5.83, []
125
+ helper_generate_track_sample_data kit, "X", 5.83, sl[0..4]
126
+ helper_generate_track_sample_data kit, "X.", 5.83, sl[0..4] + tl
127
+ helper_generate_track_sample_data kit, ".X", 5.83, tl[0..4] + sl
128
+ helper_generate_track_sample_data kit, "...X.", 5.83, (z * 17) + sl + tl
129
+ helper_generate_track_sample_data kit, ".X.XX.", 5.83, tl[0..4] + sl + tl + sl + sl + tl[0..4]
130
+ helper_generate_track_sample_data kit, "...", 5.83, z * 17
131
+
132
+ # 3A.) Tick sample length is shorter than the sound sample data. Overflow will now occur!
133
+ helper_generate_track_sample_data kit, "", 2, [], []
134
+ helper_generate_track_sample_data kit, "X", 2, ss, so
135
+ helper_generate_track_sample_data kit, "X.", 2, s, []
136
+ helper_generate_track_sample_data kit, ".X", 2, ts + ss, so
137
+ helper_generate_track_sample_data kit, "...X.", 2, (ts * 3) + s, []
138
+ helper_generate_track_sample_data kit, ".X.XX.", 2, ts + s + ss + s, []
139
+ helper_generate_track_sample_data kit, "...", 2, z * 6, []
140
+
141
+ # 3B.) Tick sample length is shorter than sound sample data, such that a beat other than the final one
142
+ # would extend past the end of the rhythm if not cut off. Make sure that the sample data array doesn't
143
+ # inadvertently lengthen as a result.
144
+ #helper_generate_track_sample_data kit, "XX", 1, [-100, -100], [200, 300, -400]
145
+
146
+ # 3C.) Tick sample length is shorter than the sound sample data, but not by an integer amount.
147
+ #
148
+ # Each step of 1.83 samples should end on the following boundaries:
149
+ # Tick: 1, 2, 3, 4, 5, 6
150
+ # Raw: 0.0, 1.83, 3.66, 5.49, 7.32, 9.15, 10.98
151
+ # Quantized: 0, 1, 3, 5, 7, 9, 10
152
+ helper_generate_track_sample_data kit, "", 1.83, []
153
+ helper_generate_track_sample_data kit, "X", 1.83, s[0..0], s[1..3]
154
+ helper_generate_track_sample_data kit, "X.", 1.83, s[0..2], s[3..3]
155
+ helper_generate_track_sample_data kit, ".X", 1.83, z + s[0..1], s[2..3]
156
+ helper_generate_track_sample_data kit, "...X.", 1.83, (z * 5) + s, []
157
+ helper_generate_track_sample_data kit, ".X.XX.", 1.83, z + s + ss + s[0..2], s[3..3]
158
+ helper_generate_track_sample_data kit, "...", 1.83, z * 5, []
159
+ end
160
+ end
161
+
162
+ def helper_generate_track_sample_data(kit, rhythm, step_sample_length, expected_primary, expected_overflow = [])
163
+ track = Track.new("foo", rhythm)
164
+ engine = MockAudioEngine.new(Song.new(), kit)
165
+ engine.step_sample_length = step_sample_length
166
+ actual = engine.generate_track_sample_data(track, kit.get_sample_data("S"))
167
+
168
+ assert_equal(Hash, actual.class)
169
+ assert_equal(["overflow", "primary"], actual.keys.map{|key| key.to_s}.sort)
170
+ assert_equal(expected_primary, actual[:primary])
171
+ assert_equal(expected_overflow, actual[:overflow])
172
+ end
173
+
174
+ def test_composite_pattern_tracks
175
+ no_overflow_pattern = Pattern.new("no_overflow")
176
+ no_overflow_pattern.track "S", "X..."
177
+ no_overflow_pattern.track "SO", "X.X."
178
+ no_overflow_pattern.track "S", "X.XX"
179
+
180
+ overflow_pattern = Pattern.new("overflow")
181
+ overflow_pattern.track "S", "X..X"
182
+ overflow_pattern.track "SO", "XX.X"
183
+ overflow_pattern.track "SL", ".X.X"
184
+
185
+
186
+ # Simple case, no overflow (stereo)
187
+ engine = MockAudioEngine.new(Song.new(), MONO_KIT)
188
+ engine.step_sample_length = 4
189
+ primary, overflow = engine.composite_pattern_tracks(no_overflow_pattern)
190
+ assert_equal([
191
+ -100 + 300 + -100, 200 + -400 + 200, 300 + 0 + 300, -400 + 0 + -400,
192
+ 0 + 0 + 0, 0 + 0 + 0, 0 + 0 + 0, 0 + 0 + 0,
193
+ 0 + 300 + -100, 0 + -400 + 200, 0 + 0 + 300, 0 + 0 + -400,
194
+ 0 + 0 + -100, 0 + 0 + 200, 0 + 0 + 300, 0 + 0 + -400,
195
+ ],
196
+ primary)
197
+ assert_equal({"S" => [], "SO" => [], "S2" => []}, overflow)
198
+
199
+
200
+ # Simple case, no overflow (stereo)
201
+ engine = MockAudioEngine.new(Song.new(), STEREO_KIT)
202
+ engine.step_sample_length = 4
203
+ primary, overflow = engine.composite_pattern_tracks(no_overflow_pattern)
204
+ assert_equal([
205
+ [-100 + 300 + -100, 800 + -600 + 800],
206
+ [200 + -400 + 200, -700 + 400 + -700],
207
+ [300 + 0 + 300, -600 + 0 + -600],
208
+ [-400 + 0 + -400, 400 + 0 + 400],
209
+ [0 + 0 + 0, 0 + 0 + 0],
210
+ [0 + 0 + 0, 0 + 0 + 0],
211
+ [0 + 0 + 0, 0 + 0 + 0],
212
+ [0 + 0 + 0, 0 + 0 + 0],
213
+ [0 + 300 + -100, 0 + -600 + 800],
214
+ [0 + -400 + 200, 0 + 400 + -700],
215
+ [0 + 0 + 300, 0 + 0 + -600],
216
+ [0 + 0 + -400, 0 + 0 + 400],
217
+ [0 + 0 + -100, 0 + 0 + 800],
218
+ [0 + 0 + 200, 0 + 0 + -700],
219
+ [0 + 0 + 300, 0 + 0 + -600],
220
+ [0 + 0 + -400, 0 + 0 + 400],
221
+ ],
222
+ primary)
223
+ assert_equal({"S" => [], "SO" => [], "S2" => []}, overflow)
224
+
225
+
226
+ # Some overflow (mono)
227
+ engine = MockAudioEngine.new(Song.new(), MONO_KIT)
228
+ engine.step_sample_length = 3
229
+ primary, overflow = engine.composite_pattern_tracks(overflow_pattern)
230
+ assert_equal([
231
+ -100 + 300 + 0, 200 + -400 + 0, 300 + 0 + 0,
232
+ -400 + 300 + -100, 0 + -400 + 200, 0 + 0 + 300,
233
+ 0 + 0 + -400, 0 + 0 + 0, 0 + 0 + 0,
234
+ -100 + 300 + -100, 200 + -400 + 200, 300 + 0 + 300,
235
+ ],
236
+ primary)
237
+ assert_equal({"S" => [-400], "SO" => [], "SL" => [-400, 0, 0]}, overflow)
238
+
239
+
240
+ # Some overflow (stereo)
241
+ engine = MockAudioEngine.new(Song.new(), STEREO_KIT)
242
+ engine.step_sample_length = 3
243
+ primary, overflow = engine.composite_pattern_tracks(overflow_pattern)
244
+ assert_equal([
245
+ [-100 + 300 + 0, 800 + -600 + 0],
246
+ [200 + -400 + 0, -700 + 400 + 0],
247
+ [300 + 0 + 0, -600 + 0 + 0],
248
+ [-400 + 300 + -100, 400 + -600 + 800],
249
+ [0 + -400 + 200, 0 + 400 + -700],
250
+ [0 + 0 + 300, 0 + 0 + -600],
251
+ [0 + 0 + -400, 0 + 0 + 400],
252
+ [0 + 0 + 0, 0 + 0 + 0],
253
+ [0 + 0 + 0, 0 + 0 + 0],
254
+ [-100 + 300 + -100, 800 + -600 + 800],
255
+ [200 + -400 + 200, -700 + 400 + -700],
256
+ [300 + 0 + 300, -600 + 0 + -600],
257
+ ],
258
+ primary)
259
+ assert_equal({"S" => [[-400, 400]], "SO" => [], "SL" => [[-400, 400], [0, 0], [0, 0]]}, overflow)
260
+ end
261
+ end
@@ -0,0 +1,45 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'test/includes'
4
+
5
+ class AudioUtilsTest < Test::Unit::TestCase
6
+ def test_composite
7
+ # Mono empty arrays
8
+ assert_equal([], AudioUtils.composite([], 1))
9
+ assert_equal([], AudioUtils.composite([[]], 1))
10
+ assert_equal([], AudioUtils.composite([[], [], [], []], 1))
11
+
12
+ # Stereo empty arrays
13
+ assert_equal([], AudioUtils.composite([], 2))
14
+ assert_equal([], AudioUtils.composite([[]], 2))
15
+ assert_equal([], AudioUtils.composite([[], [], [], []], 2))
16
+
17
+ # Mono
18
+ assert_equal([10, 20, 30, 40], AudioUtils.composite([[10, 20, 30, 40]], 1))
19
+ assert_equal([10, 20, 30, 40], AudioUtils.composite([[10, 20, 30, 40], []], 1))
20
+ assert_equal([30, 50, 70, -10], AudioUtils.composite([[10, 20, 30, 40], [20, 30, 40, -50]], 1))
21
+ assert_equal([70, 80, 60], AudioUtils.composite([[20, 30], [10], [40, 50, 60]], 1))
22
+
23
+ # Stereo
24
+ assert_equal([[10, 20], [30, 40]], AudioUtils.composite([[[10, 20], [30, 40]]], 2))
25
+ assert_equal([[10, 20], [30, 40]], AudioUtils.composite([[[10, 20], [30, 40]], []], 2))
26
+ assert_equal([[30, 50], [70, -10]], AudioUtils.composite([[[10, 20], [30, 40]], [[20, 30], [40, -50]]], 2))
27
+ assert_equal([[90, 120], [120, 140], [100, 110]], AudioUtils.composite([[[20, 30], [40, 50]], [[10, 20]], [[60, 70], [80, 90], [100, 110]]], 2))
28
+ end
29
+
30
+ def test_scale
31
+ assert_equal([], AudioUtils.scale([], 1, 5))
32
+ assert_equal([], AudioUtils.scale([], 2, 5))
33
+ assert_equal([100, 200, 300, 400, 500], AudioUtils.scale([100, 200, 300, 400, 500], 1, 1))
34
+ assert_equal([20, 40, 60, 80, 100], AudioUtils.scale([100, 200, 300, 400, 500], 1, 5))
35
+
36
+ assert_equal([[100, 200], [300, 400], [500, 600]], AudioUtils.scale([[100, 200], [300, 400], [500, 600]], 2, 1))
37
+ assert_equal([[20, 40], [60, 80], [100, 120]], AudioUtils.scale([[100, 200], [300, 400], [500, 600]], 2, 5))
38
+ end
39
+
40
+ def test_step_sample_length
41
+ assert_equal(6615.0, AudioUtils.step_sample_length(44100, 100))
42
+ assert_equal(3307.5, AudioUtils.step_sample_length(44100, 200))
43
+ assert_equal(3307.5, AudioUtils.step_sample_length(22050, 100))
44
+ end
45
+ end
@@ -1,7 +1,7 @@
1
- # Invalid song, since the structure references a non-existent pattern
1
+ # Invalid song, since the flow references a non-existent pattern
2
2
  Song:
3
3
  Tempo: 100
4
- Structure:
4
+ Flow:
5
5
  - Verse: x2
6
6
  - Chorus: x1
7
7