beats 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 386d1a9b9e83c848b2f9ae9c802f1ce9c717c99a
4
- data.tar.gz: 287a1390bed816c4bcf65bb5749af04297d8bfd9
3
+ metadata.gz: b76857d49851dc03ef574dadcbbc01aff5aaea9f
4
+ data.tar.gz: df598d61cfaa1a0ecb8f1bd493dc8b1f0436869e
5
5
  SHA512:
6
- metadata.gz: dd8197163c74429d9e1ba1c5ded23af90f423ac48702e085732c5476e788064f497854d585da512f0babd1fb2ad85119fa21caea591e821105e4133cfa8ab443
7
- data.tar.gz: 24f11590f134ee21e801d463c6be28334f009ab2da5294b27aa23af2db7ab27550817a61a6db1c3b699ae821e351f1c892da2f2644252ed2bd8faebe4f908236
6
+ metadata.gz: 59bd59d74a085c9d60c0f4584a9a6704c58aa05e4856d24884e4e5de622e578cd9e7a1a47edb35e172b8b1ff2f67c0386b7d99db238dcaf11d3f366a57ec42d7
7
+ data.tar.gz: 7ab6c3283977bcda76772ae287e67d2d120fdd8623fe777364dfc70bd6a1250b3c50e96bec6e047d7da199dd6ec6877b02cbf4e0b7d9009a480becdf92498656
@@ -35,7 +35,7 @@ For more, check out [beatsdrummachine.com](http://beatsdrummachine.com)
35
35
  Installation
36
36
  ------------
37
37
 
38
- To install the latest stable version (2.0.0) from [rubygems.org](http://rubygems.org/gems/beats), run the following from the command line:
38
+ To install the latest stable version (2.1.0) from [rubygems.org](http://rubygems.org/gems/beats), run the following from the command line:
39
39
 
40
40
  gem install beats
41
41
 
@@ -57,7 +57,72 @@ Check out [this tutorial at beatsdrummachine.com](http://beatsdrummachine.com/tu
57
57
  What's New
58
58
  ----------
59
59
 
60
- The latest stable version of Beats is 2.0.0, released on September 4, 2017. It is primarily a modernization release, and contains some relatively small backwards incompatible changes.
60
+ The latest version of Beats is 2.1.0, released on September 9, 2017.
61
+
62
+ This version adds support for *composite sounds*. That is, sounds that are made by combining two or more sounds together. They are a more succinct way of writing songs where multiple tracks play the same rhythm.
63
+
64
+ Composite sounds can be defined in the Kit by putting multiple sound files in an array:
65
+
66
+ Kit:
67
+ - bass: bass.wav # A traditional non-composite sound
68
+ - combo_snare: [clap.wav, 808_snare.wav] # A composite sound
69
+
70
+ The `combo_snare` sound above is a composite sound made by combining `clap.wav` and `808_snare.wav` together. It can then be used in a pattern:
71
+
72
+ Verse:
73
+ - bass: X.......X.......
74
+ - combo_snare: ....X.......X...
75
+
76
+ This is equivalent to the following song:
77
+
78
+ Kit:
79
+ - bass: bass.wav
80
+ - clap: clap.wav
81
+ - snare: 808_snare.wav
82
+
83
+ Verse:
84
+ - bass: X.......X.......
85
+ - clap: ....X.......X...
86
+ - snare: ....X.......X...
87
+
88
+ When using the `-s` command-line option to write each track to its own *.wav file, each sub-sound in a composite sound will be written to its own file. For example, this song:
89
+
90
+ Kit:
91
+ - combo_snare: [clap.wav, 808_snare.wav]
92
+
93
+ Verse:
94
+ - combo_snare: X...X...X...X...
95
+
96
+ ...will be written to two different files, `combo_snare-clap.wav` and `combo_snare-808_snare.wav`, when using the `-s` option.
97
+
98
+ Finally, when defining a track in a pattern, multiple sounds can be given in an array as a composite sound. Kit sounds and non-Kit sounds can be used together:
99
+
100
+ Kit:
101
+ - bass: bass.wav
102
+ - combo_snare: [clap.wav, 808_snare.wav]
103
+
104
+ Verse:
105
+ - [bass, combo_snare, other_sound.wav]: X...X...X...X...
106
+
107
+ This is a equivalent to:
108
+
109
+ Kit:
110
+ - bass: bass.wav
111
+ - clap: clap.wav
112
+ - snare: 808_snare.wav
113
+
114
+ Verse:
115
+ - bass: X...X...X...X...
116
+ - clap: X...X...X...X...
117
+ - snare: X...X...X...X...
118
+ - other_sound.wav: X...X...X...X...
119
+
120
+
121
+
122
+ What's Slightly Less New
123
+ ------------------------
124
+
125
+ The previous version of Beats is 2.0.0, released on September 4, 2017. It is primarily a modernization release, and contains some relatively small backwards incompatible changes.
61
126
 
62
127
  * Track rhythms can now have spaces in them. For example, `X... .... X... ....` is now a valid rhythm. Spaces are ignored, and don't affect the rhythm. For example, `X... X...` is treated as the same rhythm as `X...X...`
63
128
  * Wave files using `WAVEFORMATEXTENSIBLE` format can now be used, due to upgrading the WaveFile gem dependency to v0.8.1 behind the scenes.
@@ -11,5 +11,5 @@ require 'beats/track'
11
11
  require 'beats/transforms/song_swinger'
12
12
 
13
13
  module Beats
14
- VERSION = "2.0.0"
14
+ VERSION = "2.1.0"
15
15
  end
@@ -14,10 +14,26 @@ module Beats
14
14
  def initialize(base_path)
15
15
  @base_path = base_path
16
16
  @labels_to_filenames = {}
17
+ @composite_replacements = {}
17
18
  end
18
19
 
19
- def add_item(label, filename)
20
- @labels_to_filenames[label] = absolute_file_name(filename)
20
+ def add_item(label, filenames)
21
+ if filenames.is_a?(Array)
22
+ if filenames.empty?
23
+ raise SoundFileNotFoundError, "Kit sound '#{label}' has an empty composite pattern (i.e. \"[]\"), which is not valid."
24
+ end
25
+
26
+ filenames.each do |filename|
27
+ unless filename.is_a?(String)
28
+ raise SoundFileNotFoundError, "The Kit sound '#{label}' contains an invalid file: '#{filename}'"
29
+ end
30
+ @labels_to_filenames["#{label}-#{File.basename(filename, ".*")}"] = absolute_file_name(filename)
31
+ end
32
+
33
+ @composite_replacements[label] = filenames.map {|filename| "#{label}-#{File.basename(filename, ".*")}" }
34
+ else
35
+ @labels_to_filenames[label] = absolute_file_name(filenames)
36
+ end
21
37
  end
22
38
 
23
39
  def has_label?(label)
@@ -45,6 +61,8 @@ module Beats
45
61
  Kit.new(labels_to_buffers, num_channels, BITS_PER_SAMPLE)
46
62
  end
47
63
 
64
+ attr_reader :composite_replacements
65
+
48
66
  private
49
67
 
50
68
  # Converts relative path into absolute path. Note that this will also handle
@@ -137,8 +137,7 @@ module Beats
137
137
  replacements = {}
138
138
 
139
139
  # Pattern names are sorted to ensure predictable pattern replacement. Makes tests easier to write.
140
- # Sort function added manually because Ruby 1.8 doesn't know how to sort symbols...
141
- pattern_names = patterns.keys.sort {|x, y| x.to_s <=> y.to_s }
140
+ pattern_names = patterns.keys.sort
142
141
 
143
142
  # Detect duplicates
144
143
  pattern_names.each do |pattern_name|
@@ -11,11 +11,11 @@ module Beats
11
11
  NO_SONG_HEADER_ERROR_MSG =
12
12
  "Song must have a header. Here's an example:
13
13
 
14
- Song:
15
- Tempo: 120
16
- Flow:
17
- - Verse: x2
18
- - Chorus: x2"
14
+ Song:
15
+ Tempo: 120
16
+ Flow:
17
+ - Verse: x2
18
+ - Chorus: x2"
19
19
 
20
20
  def initialize
21
21
  end
@@ -30,8 +30,9 @@ module Beats
30
30
  end
31
31
 
32
32
  song = Song.new
33
+ kit_builder = KitBuilder.new(base_path)
33
34
 
34
- # 1.) Set tempo
35
+ # Set tempo
35
36
  begin
36
37
  unless raw_song_components[:tempo].nil?
37
38
  song.tempo = raw_song_components[:tempo]
@@ -40,26 +41,26 @@ module Beats
40
41
  raise ParseError, "#{detail}"
41
42
  end
42
43
 
43
- # 2.) Build the kit
44
+ # Add sounds defined in the Kit section
44
45
  begin
45
- kit = build_kit(base_path, raw_song_components[:kit], raw_song_components[:patterns])
46
+ add_kit_sounds_from_kit(kit_builder, raw_song_components[:kit])
46
47
  rescue KitBuilder::SoundFileNotFoundError => detail
47
48
  raise ParseError, "#{detail}"
48
49
  rescue KitBuilder::InvalidSoundFormatError => detail
49
50
  raise ParseError, "#{detail}"
50
51
  end
51
52
 
52
- # 3.) Load patterns
53
- add_patterns_to_song(song, raw_song_components[:patterns])
53
+ # Load patterns
54
+ add_patterns_to_song(song, kit_builder, raw_song_components[:patterns])
54
55
 
55
- # 4.) Set flow
56
+ # Set flow
56
57
  if raw_song_components[:flow].nil?
57
58
  raise ParseError, "Song must have a Flow section in the header."
58
59
  else
59
60
  set_song_flow(song, raw_song_components[:flow])
60
61
  end
61
62
 
62
- # 5.) Swing, if swing flag is set
63
+ # Swing, if swing flag is set
63
64
  if raw_song_components[:swing]
64
65
  begin
65
66
  song = Transforms::SongSwinger.transform(song, raw_song_components[:swing])
@@ -68,6 +69,16 @@ module Beats
68
69
  end
69
70
  end
70
71
 
72
+ # Build the final kit
73
+ begin
74
+ add_kit_sounds_from_patterns(kit_builder, song.patterns)
75
+ kit = kit_builder.build_kit
76
+ rescue KitBuilder::SoundFileNotFoundError => detail
77
+ raise ParseError, "#{detail}"
78
+ rescue KitBuilder::InvalidSoundFormatError => detail
79
+ raise ParseError, "#{detail}"
80
+ end
81
+
71
82
  return song, kit
72
83
  end
73
84
 
@@ -104,9 +115,7 @@ module Beats
104
115
  return raw_song_components
105
116
  end
106
117
 
107
- def build_kit(base_path, raw_kit, raw_patterns)
108
- kit_builder = KitBuilder.new(base_path)
109
-
118
+ def add_kit_sounds_from_kit(kit_builder, raw_kit)
110
119
  # Add sounds defined in the Kit section of the song header
111
120
  # Converts [{a=>1}, {b=>2}, {c=>3}] from raw YAML to {a=>1, b=>2, c=>3}
112
121
  # TODO: Raise error is same name is defined more than once in the Kit
@@ -115,45 +124,54 @@ module Beats
115
124
  kit_builder.add_item(kit_item.keys.first, kit_item.values.first)
116
125
  end
117
126
  end
127
+ end
118
128
 
129
+ def add_kit_sounds_from_patterns(kit_builder, patterns)
119
130
  # Add sounds not defined in Kit section, but used in individual tracks
120
- raw_patterns.keys.each do |key|
121
- track_list = raw_patterns[key]
122
-
123
- unless track_list.nil?
124
- track_list.each do |track_definition|
125
- track_name = track_definition.keys.first
126
- track_path = track_name
131
+ patterns.each do |pattern_name, pattern|
132
+ pattern.tracks.each do |track_name, track|
133
+ track_path = track.name
127
134
 
128
- if !kit_builder.has_label?(track_name)
129
- kit_builder.add_item(track_name, track_path)
130
- end
135
+ if !kit_builder.has_label?(track.name)
136
+ kit_builder.add_item(track.name, track_path)
131
137
  end
132
138
  end
133
139
  end
134
-
135
- kit_builder.build_kit
136
140
  end
137
141
 
138
- def add_patterns_to_song(song, raw_patterns)
139
- raw_patterns.keys.each do |key|
140
- new_pattern = song.pattern key.to_sym
141
-
142
- track_list = raw_patterns[key]
143
-
144
- if track_list.nil?
142
+ def add_patterns_to_song(song, kit_builder, raw_patterns)
143
+ raw_patterns.each do |pattern_name, raw_tracks|
144
+ if raw_tracks.nil?
145
145
  # TODO: Use correct capitalization of pattern name in error message
146
146
  # TODO: Possibly allow if pattern not referenced in the Flow, or has 0 repeats?
147
- raise ParseError, "Pattern '#{key}' has no tracks. It needs at least one."
147
+ raise ParseError, "Pattern '#{pattern_name}' has no tracks. It needs at least one."
148
148
  end
149
149
 
150
- track_list.each do |track_definition|
151
- track_name = track_definition.keys.first
150
+ new_pattern = song.pattern(pattern_name.to_sym)
151
+
152
+ raw_tracks.each do |raw_track|
153
+ track_names = raw_track.keys.first
154
+ rhythm = raw_track.values.first
152
155
 
153
156
  # Handle case where no track rhythm is specified (i.e. "- foo.wav:" instead of "- foo.wav: X.X.X.X.")
154
- track_definition[track_name] ||= ""
157
+ rhythm = "" if rhythm.nil?
155
158
 
156
- new_pattern.track track_name, track_definition[track_name]
159
+ track_names = Array(track_names)
160
+ if track_names.empty?
161
+ raise ParseError, "Pattern '#{pattern_name}' has an empty composite pattern (i.e. \"[]\"), which is not valid."
162
+ end
163
+
164
+ track_names.map! do |track_name|
165
+ unless track_name.is_a?(String)
166
+ raise ParseError, "'#{track_name}' in pattern '#{pattern_name}' is not a valid track sound"
167
+ end
168
+ kit_builder.composite_replacements[track_name] || track_name
169
+ end
170
+ track_names.flatten!
171
+
172
+ track_names.each do |track_name|
173
+ new_pattern.track track_name, rhythm
174
+ end
157
175
  end
158
176
  end
159
177
  end
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hihat: [] # Empty composite sounds aren't allowed
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - hihat: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: [test/sounds/hh_closed_mono_8.wav, [test/sounds/hh_open_mono_8.wav]]
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - hh_closed: .X.X.X.X
@@ -0,0 +1,17 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hihat: [test/sounds/hh_closed_mono_8.wav, test/sounds/missing.wav] # "missing.wav" is a non-existent file
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, hihat]: .X.X.X.X
17
+
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - []: .X.X.X.X # Empty composite sounds aren't allowed
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - [bass, [hh_closed]]: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, [bass, hh_closed]]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, missing]: .X.X.X.X # "missing" is not a valid file, and is not defined in the kit
@@ -0,0 +1,20 @@
1
+ # The patterns in the Flow have different capitalization from how the patterns are defined
2
+
3
+ Song:
4
+ Tempo: 100
5
+ Flow:
6
+ - verse: x1
7
+ - CHORUS: x2
8
+ - VeRsE: x1
9
+ - ChoRUS: x2
10
+ Kit:
11
+ - bass: test/sounds/bass_mono_8.wav
12
+ - snare: test/sounds/snare_mono_8.wav
13
+
14
+ Verse:
15
+ - bass: X...X...X...X...
16
+ - snare: ..............X.
17
+
18
+ Chorus:
19
+ - bass: X...X...XX..X...
20
+ - snare: ....X.......X...
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hihat: [test/sounds/hh_closed_mono_8.wav]
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - hihat: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, hihat]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hihat: [test/sounds/hh_closed_mono_8.wav, test/sounds/hh_open_mono_8.wav]
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - hihat: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, hihat]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - [bass, hh_closed]: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare, bass]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [hh_closed, test/sounds/hh_open_mono_8.wav]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hihat: [test/sounds/hh_closed_mono_8.wav, test/sounds/hh_open_mono_8.wav]
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - hihat: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [test/sounds/agogo_high_mono_8.wav, hihat, test/sounds/agogo_low_mono_8.wav]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [test/sounds/agogo_high_mono_8.wav, test/sounds/hh_open_mono_8.wav]: .X.X.X.X
@@ -0,0 +1,16 @@
1
+ Song:
2
+ Tempo: 100
3
+ Kit:
4
+ - bass: test/sounds/bass_mono_8.wav
5
+ - snare: test/sounds/snare_mono_8.wav
6
+ - hh_closed: test/sounds/hh_closed_mono_8.wav
7
+ Flow:
8
+ - Verse: x2
9
+ - Chorus: x2
10
+
11
+ Verse:
12
+ - bass: X...X...
13
+ - snare: ..X...X.
14
+ Chorus:
15
+ - bass: XXXXXXXX
16
+ - [snare]: .X.X.X.X
@@ -3,20 +3,6 @@ require 'includes'
3
3
  class SongParserTest < Minitest::Test
4
4
  FIXTURE_BASE_PATH = File.dirname(__FILE__) + "/.."
5
5
 
6
- # TODO: Add fixture for track with no rhythm
7
- VALID_FIXTURES = [:no_tempo,
8
- :fractional_tempo,
9
- :repeats_not_specified,
10
- :pattern_with_overflow,
11
- :example_no_kit,
12
- :example_with_kit,
13
- :example_with_empty_track,
14
- :track_with_spaces,
15
- :multiple_tracks_same_sound,
16
- :example_swung_8th,
17
- :example_swung_16th,
18
- :example_unswung]
19
-
20
6
  INVALID_FIXTURES = [:bad_repeat_count,
21
7
  :bad_flow,
22
8
  :bad_tempo,
@@ -26,46 +12,62 @@ class SongParserTest < Minitest::Test
26
12
  :no_flow,
27
13
  :with_structure,
28
14
  :pattern_with_no_tracks,
15
+ :track_with_composite_non_existent_sound,
16
+ :kit_with_composite_non_existent_sound,
17
+ :track_with_composite_empty_sound,
18
+ :kit_with_composite_empty_sound,
19
+ :track_with_composite_nested_sounds,
20
+ :kit_with_composite_nested_sounds,
29
21
  :leading_bar_line,
30
22
  :sound_in_kit_not_found,
31
23
  :sound_in_track_not_found,
32
24
  :sound_in_kit_wrong_format,
33
25
  :sound_in_track_wrong_format]
34
26
 
35
- def self.load_fixture(fixture_name)
36
- SongParser.new.parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
37
- end
27
+ # TODO: Add somes tests to validate the Kits
38
28
 
39
- def self.generate_test_data
40
- test_songs = {}
41
- test_kits = {}
29
+ def test_no_tempo
30
+ song, kit = load_fixture("valid/no_tempo.txt")
42
31
 
43
- VALID_FIXTURES.each do |fixture_name|
44
- song, kit = load_fixture("valid/#{fixture_name}.txt")
45
- test_songs[fixture_name] = song
46
- test_kits[fixture_name] = kit
47
- end
32
+ assert_equal(120, song.tempo)
33
+ assert_equal([:verse], song.flow)
34
+ end
48
35
 
49
- return test_songs, test_kits
36
+ def test_fractional_tempo
37
+ song, kit = load_fixture("valid/fractional_tempo.txt")
38
+
39
+ assert_equal(95.764, song.tempo)
40
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
50
41
  end
51
42
 
52
- # TODO: Add somes tests to validate the Kits
53
- def test_valid_parse
54
- test_songs, test_kits = SongParserTest.generate_test_data
43
+ def test_repeats_not_specified
44
+ song, kit = load_fixture("valid/repeats_not_specified.txt")
55
45
 
56
- assert_equal(120, test_songs[:no_tempo].tempo)
57
- assert_equal([:verse], test_songs[:no_tempo].flow)
46
+ assert_equal(100, song.tempo)
47
+ assert_equal([:verse], song.flow)
48
+ end
58
49
 
59
- assert_equal(95.764, test_songs[:fractional_tempo].tempo)
60
- assert_equal([:verse, :verse, :chorus, :chorus], test_songs[:fractional_tempo].flow)
50
+ def test_flow_patterns_different_capitalization
51
+ song, kit = load_fixture("valid/example_flow_patterns_different_capitalization.txt")
61
52
 
62
- assert_equal(100, test_songs[:repeats_not_specified].tempo)
63
- assert_equal([:verse], test_songs[:repeats_not_specified].flow)
53
+ assert_equal(100, song.tempo)
54
+ assert_equal([:verse, :chorus, :chorus, :verse, :chorus, :chorus], song.flow)
55
+ assert_equal(2, song.patterns.length)
56
+ assert_equal(2, song.patterns[:verse].tracks.length)
57
+ assert_equal("X...X...X...X...", song.patterns[:verse].tracks["bass"].rhythm)
58
+ assert_equal("..............X.", song.patterns[:verse].tracks["snare"].rhythm)
59
+ assert_equal(2, song.patterns[:chorus].tracks.length)
60
+ assert_equal("X...X...XX..X...", song.patterns[:chorus].tracks["bass"].rhythm)
61
+ assert_equal("....X.......X...", song.patterns[:chorus].tracks["snare"].rhythm)
62
+ end
63
+
64
+ def test_song_with_unused_kit
65
+ no_kit_song, no_kit_kit = load_fixture("valid/example_no_kit.txt")
66
+ kit_song, kit_kit = load_fixture("valid/example_with_kit.txt")
64
67
 
65
68
  # These two songs should be the same, except that one uses a kit in the song header
66
69
  # and the other doesn't.
67
- [:example_no_kit, :example_with_kit].each do |song_key|
68
- song = test_songs[song_key]
70
+ [no_kit_song, kit_song].each do |song|
69
71
  assert_equal([:verse, :verse,
70
72
  :chorus, :chorus,
71
73
  :verse, :verse,
@@ -79,20 +81,29 @@ class SongParserTest < Minitest::Test
79
81
  assert_equal(5, song.patterns[:chorus].tracks.length)
80
82
  assert_equal(1, song.patterns[:bridge].tracks.length)
81
83
  end
84
+ end
85
+
86
+ def test_empty_track
87
+ song, kit = load_fixture("valid/example_with_empty_track.txt")
82
88
 
83
- song = test_songs[:example_with_empty_track]
84
89
  assert_equal(1, song.patterns.length)
85
90
  assert_equal(2, song.patterns[:verse].tracks.length)
86
91
  assert_equal("........", song.patterns[:verse].tracks["test/sounds/bass_mono_8.wav"].rhythm)
87
92
  assert_equal("X...X...", song.patterns[:verse].tracks["test/sounds/snare_mono_8.wav"].rhythm)
93
+ end
94
+
95
+ def test_track_with_spaces
96
+ song, kit = load_fixture("valid/track_with_spaces.txt")
88
97
 
89
- song = test_songs[:track_with_spaces]
90
98
  assert_equal(1, song.patterns.length)
91
99
  assert_equal(2, song.patterns[:verse].tracks.length)
92
100
  assert_equal("X...X...X...X...", song.patterns[:verse].tracks["bass"].rhythm)
93
101
  assert_equal("....X.......X...", song.patterns[:verse].tracks["snare"].rhythm)
102
+ end
103
+
104
+ def test_multiple_tracks_same_sound
105
+ song, kit = load_fixture("valid/multiple_tracks_same_sound.txt")
94
106
 
95
- song = test_songs[:multiple_tracks_same_sound]
96
107
  assert_equal(2, song.patterns.length)
97
108
  assert_equal(7, song.patterns[:verse].tracks.length)
98
109
  assert_equal(["agogo", "bass", "bass2", "bass3", "bass4", "hh_closed", "snare"],
@@ -112,8 +123,11 @@ class SongParserTest < Minitest::Test
112
123
  assert_equal("..X..X..X..X..X.", song.patterns[:chorus].tracks["bass2"].rhythm)
113
124
  assert_equal("...........X....", song.patterns[:chorus].tracks["test/sounds/tom4_mono_16.wav"].rhythm)
114
125
  assert_equal("..............X.", song.patterns[:chorus].tracks["test/sounds/tom2_mono_16.wav"].rhythm)
126
+ end
127
+
128
+ def test_swung_8
129
+ song, kit = load_fixture("valid/example_swung_8th.txt")
115
130
 
116
- song = test_songs[:example_swung_8th]
117
131
  assert_equal(180, song.tempo)
118
132
  assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
119
133
  assert_equal(2, song.patterns.length)
@@ -123,8 +137,11 @@ class SongParserTest < Minitest::Test
123
137
  assert_equal(2, song.patterns[:chorus].tracks.length)
124
138
  assert_equal("X.X.XXX.X.XX", song.patterns[:chorus].tracks["bass"].rhythm)
125
139
  assert_equal("..X..X..X..X", song.patterns[:chorus].tracks["snare"].rhythm)
140
+ end
141
+
142
+ def test_swung_16
143
+ song, kit = load_fixture("valid/example_swung_16th.txt")
126
144
 
127
- song = test_songs[:example_swung_16th]
128
145
  assert_equal(180, song.tempo)
129
146
  assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
130
147
  assert_equal(2, song.patterns.length)
@@ -134,8 +151,11 @@ class SongParserTest < Minitest::Test
134
151
  assert_equal(2, song.patterns[:chorus].tracks.length)
135
152
  assert_equal("X.XX.XX.XX.X", song.patterns[:chorus].tracks["bass"].rhythm)
136
153
  assert_equal("..X..X..X..X", song.patterns[:chorus].tracks["snare"].rhythm)
154
+ end
155
+
156
+ def test_unswung_song
157
+ song, kit = load_fixture("valid/example_unswung.txt")
137
158
 
138
- song = test_songs[:example_unswung]
139
159
  assert_equal(120, song.tempo)
140
160
  assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
141
161
  assert_equal(2, song.patterns.length)
@@ -147,15 +167,131 @@ class SongParserTest < Minitest::Test
147
167
  assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
148
168
  end
149
169
 
170
+ def test_track_with_composite_kit_sounds
171
+ song, kit = load_fixture("valid/track_with_composite_kit_sounds.txt")
172
+
173
+ assert_equal(100, song.tempo)
174
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
175
+ assert_equal(2, song.patterns.length)
176
+ assert_equal(3, song.patterns[:verse].tracks.length)
177
+ assert_equal("X...X...", song.patterns[:verse].tracks["bass"].rhythm)
178
+ assert_equal("X...X...", song.patterns[:verse].tracks["hh_closed"].rhythm)
179
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
180
+ assert_equal(3, song.patterns[:chorus].tracks.length)
181
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
182
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
183
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["bass2"].rhythm)
184
+ end
185
+
186
+ def test_track_with_composite_non_kit_sound
187
+ song, kit = load_fixture("valid/track_with_composite_non_kit_sound.txt")
188
+
189
+ assert_equal(100, song.tempo)
190
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
191
+ assert_equal(2, song.patterns.length)
192
+ assert_equal(2, song.patterns[:verse].tracks.length)
193
+ assert_equal("X...X...", song.patterns[:verse].tracks["bass"].rhythm)
194
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
195
+ assert_equal(3, song.patterns[:chorus].tracks.length)
196
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
197
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["test/sounds/agogo_high_mono_8.wav"].rhythm)
198
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["test/sounds/hh_open_mono_8.wav"].rhythm)
199
+ end
200
+
201
+ def test_track_with_composite_mix_kit_and_not_kit_sound
202
+ song, kit = load_fixture("valid/track_with_composite_mix_kit_and_not_kit_sound.txt")
203
+
204
+ assert_equal(100, song.tempo)
205
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
206
+ assert_equal(2, song.patterns.length)
207
+ assert_equal(2, song.patterns[:verse].tracks.length)
208
+ assert_equal("X...X...", song.patterns[:verse].tracks["bass"].rhythm)
209
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
210
+ assert_equal(3, song.patterns[:chorus].tracks.length)
211
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
212
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hh_closed"].rhythm)
213
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["test/sounds/hh_open_mono_8.wav"].rhythm)
214
+ end
215
+
216
+ def test_track_with_composite_mix_kit_and_not_kit_sound_2
217
+ song, kit = load_fixture("valid/track_with_composite_mix_kit_and_not_kit_sound_2.txt")
218
+
219
+ assert_equal(100, song.tempo)
220
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
221
+ assert_equal(2, song.patterns.length)
222
+ assert_equal(3, song.patterns[:verse].tracks.length)
223
+ assert_equal("X...X...", song.patterns[:verse].tracks["hihat-hh_closed_mono_8"].rhythm)
224
+ assert_equal("X...X...", song.patterns[:verse].tracks["hihat-hh_open_mono_8"].rhythm)
225
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
226
+ assert_equal(5, song.patterns[:chorus].tracks.length)
227
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
228
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["test/sounds/agogo_high_mono_8.wav"].rhythm)
229
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hihat-hh_closed_mono_8"].rhythm)
230
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hihat-hh_open_mono_8"].rhythm)
231
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["test/sounds/agogo_low_mono_8.wav"].rhythm)
232
+ end
233
+
234
+ def test_track_with_composite_single_sound
235
+ song, kit = load_fixture("valid/track_with_composite_single_sound.txt")
236
+
237
+ assert_equal(100, song.tempo)
238
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
239
+ assert_equal(2, song.patterns.length)
240
+ assert_equal(2, song.patterns[:verse].tracks.length)
241
+ assert_equal("X...X...", song.patterns[:verse].tracks["bass"].rhythm)
242
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
243
+ assert_equal(2, song.patterns[:chorus].tracks.length)
244
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
245
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
246
+ end
247
+
248
+ def test_kit_with_composite_sounds
249
+ song, kit = load_fixture("valid/kit_with_composite_sounds.txt")
250
+
251
+ assert_equal(100, song.tempo)
252
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
253
+ assert_equal(2, song.patterns.length)
254
+ assert_equal(3, song.patterns[:verse].tracks.length)
255
+ assert_equal("X...X...", song.patterns[:verse].tracks["hihat-hh_closed_mono_8"].rhythm)
256
+ assert_equal("X...X...", song.patterns[:verse].tracks["hihat-hh_open_mono_8"].rhythm)
257
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
258
+ assert_equal(4, song.patterns[:chorus].tracks.length)
259
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
260
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
261
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hihat-hh_closed_mono_8"].rhythm)
262
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hihat-hh_open_mono_8"].rhythm)
263
+ end
264
+
265
+ def test_kit_with_composite_single_sound
266
+ song, kit = load_fixture("valid/kit_with_composite_single_sound.txt")
267
+
268
+ assert_equal(100, song.tempo)
269
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
270
+ assert_equal(2, song.patterns.length)
271
+ assert_equal(2, song.patterns[:verse].tracks.length)
272
+ assert_equal("X...X...", song.patterns[:verse].tracks["hihat-hh_closed_mono_8"].rhythm)
273
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
274
+ assert_equal(3, song.patterns[:chorus].tracks.length)
275
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
276
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
277
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["hihat-hh_closed_mono_8"].rhythm)
278
+ end
279
+
150
280
  def test_invalid_parse
151
281
  INVALID_FIXTURES.each do |fixture|
152
282
  assert_raises(SongParser::ParseError) do
153
- song = SongParserTest.load_fixture("invalid/#{fixture}.txt")
283
+ song, kit = load_fixture("invalid/#{fixture}.txt")
154
284
  end
155
285
  end
156
286
 
157
287
  assert_raises(Track::InvalidRhythmError) do
158
- song = SongParserTest.load_fixture("invalid/bad_rhythm.txt")
288
+ song, kit = load_fixture("invalid/bad_rhythm.txt")
159
289
  end
160
290
  end
291
+
292
+ private
293
+
294
+ def load_fixture(fixture_name)
295
+ SongParser.new.parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
296
+ end
161
297
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beats
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Strait
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-04 00:00:00.000000000 Z
11
+ date: 2017-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: wavefile
@@ -90,6 +90,9 @@ files:
90
90
  - test/fixtures/invalid/bad_swing_rate_1.txt
91
91
  - test/fixtures/invalid/bad_swing_rate_2.txt
92
92
  - test/fixtures/invalid/bad_tempo.txt
93
+ - test/fixtures/invalid/kit_with_composite_empty_sound.txt
94
+ - test/fixtures/invalid/kit_with_composite_nested_sounds.txt
95
+ - test/fixtures/invalid/kit_with_composite_non_existent_sound.txt
93
96
  - test/fixtures/invalid/leading_bar_line.txt
94
97
  - test/fixtures/invalid/no_flow.txt
95
98
  - test/fixtures/invalid/no_header.txt
@@ -98,7 +101,11 @@ files:
98
101
  - test/fixtures/invalid/sound_in_kit_wrong_format.txt
99
102
  - test/fixtures/invalid/sound_in_track_not_found.txt
100
103
  - test/fixtures/invalid/sound_in_track_wrong_format.txt
104
+ - test/fixtures/invalid/track_with_composite_empty_sound.txt
105
+ - test/fixtures/invalid/track_with_composite_nested_sounds.txt
106
+ - test/fixtures/invalid/track_with_composite_non_existent_sound.txt
101
107
  - test/fixtures/invalid/with_structure.txt
108
+ - test/fixtures/valid/example_flow_patterns_different_capitalization.txt
102
109
  - test/fixtures/valid/example_mono_16.txt
103
110
  - test/fixtures/valid/example_mono_16_base_path.txt
104
111
  - test/fixtures/valid/example_mono_8.txt
@@ -111,13 +118,19 @@ files:
111
118
  - test/fixtures/valid/example_with_empty_track.txt
112
119
  - test/fixtures/valid/example_with_kit.txt
113
120
  - test/fixtures/valid/fractional_tempo.txt
121
+ - test/fixtures/valid/kit_with_composite_single_sound.txt
122
+ - test/fixtures/valid/kit_with_composite_sounds.txt
114
123
  - test/fixtures/valid/multiple_tracks_same_sound.txt
115
124
  - test/fixtures/valid/no_tempo.txt
116
125
  - test/fixtures/valid/optimize_pattern_collision.txt
117
126
  - test/fixtures/valid/pattern_with_overflow.txt
118
127
  - test/fixtures/valid/repeats_not_specified.txt
128
+ - test/fixtures/valid/track_with_composite_kit_sounds.txt
129
+ - test/fixtures/valid/track_with_composite_mix_kit_and_not_kit_sound.txt
130
+ - test/fixtures/valid/track_with_composite_mix_kit_and_not_kit_sound_2.txt
131
+ - test/fixtures/valid/track_with_composite_non_kit_sound.txt
132
+ - test/fixtures/valid/track_with_composite_single_sound.txt
119
133
  - test/fixtures/valid/track_with_spaces.txt
120
- - test/fixtures/yaml/song_yaml.txt
121
134
  - test/includes.rb
122
135
  - test/integration_test.rb
123
136
  - test/kit_builder_test.rb
@@ -280,6 +293,9 @@ test_files:
280
293
  - test/fixtures/invalid/bad_swing_rate_1.txt
281
294
  - test/fixtures/invalid/bad_swing_rate_2.txt
282
295
  - test/fixtures/invalid/bad_tempo.txt
296
+ - test/fixtures/invalid/kit_with_composite_empty_sound.txt
297
+ - test/fixtures/invalid/kit_with_composite_nested_sounds.txt
298
+ - test/fixtures/invalid/kit_with_composite_non_existent_sound.txt
283
299
  - test/fixtures/invalid/leading_bar_line.txt
284
300
  - test/fixtures/invalid/no_flow.txt
285
301
  - test/fixtures/invalid/no_header.txt
@@ -288,7 +304,11 @@ test_files:
288
304
  - test/fixtures/invalid/sound_in_kit_wrong_format.txt
289
305
  - test/fixtures/invalid/sound_in_track_not_found.txt
290
306
  - test/fixtures/invalid/sound_in_track_wrong_format.txt
307
+ - test/fixtures/invalid/track_with_composite_empty_sound.txt
308
+ - test/fixtures/invalid/track_with_composite_nested_sounds.txt
309
+ - test/fixtures/invalid/track_with_composite_non_existent_sound.txt
291
310
  - test/fixtures/invalid/with_structure.txt
311
+ - test/fixtures/valid/example_flow_patterns_different_capitalization.txt
292
312
  - test/fixtures/valid/example_mono_16.txt
293
313
  - test/fixtures/valid/example_mono_16_base_path.txt
294
314
  - test/fixtures/valid/example_mono_8.txt
@@ -301,13 +321,19 @@ test_files:
301
321
  - test/fixtures/valid/example_with_empty_track.txt
302
322
  - test/fixtures/valid/example_with_kit.txt
303
323
  - test/fixtures/valid/fractional_tempo.txt
324
+ - test/fixtures/valid/kit_with_composite_single_sound.txt
325
+ - test/fixtures/valid/kit_with_composite_sounds.txt
304
326
  - test/fixtures/valid/multiple_tracks_same_sound.txt
305
327
  - test/fixtures/valid/no_tempo.txt
306
328
  - test/fixtures/valid/optimize_pattern_collision.txt
307
329
  - test/fixtures/valid/pattern_with_overflow.txt
308
330
  - test/fixtures/valid/repeats_not_specified.txt
331
+ - test/fixtures/valid/track_with_composite_kit_sounds.txt
332
+ - test/fixtures/valid/track_with_composite_mix_kit_and_not_kit_sound.txt
333
+ - test/fixtures/valid/track_with_composite_mix_kit_and_not_kit_sound_2.txt
334
+ - test/fixtures/valid/track_with_composite_non_kit_sound.txt
335
+ - test/fixtures/valid/track_with_composite_single_sound.txt
309
336
  - test/fixtures/valid/track_with_spaces.txt
310
- - test/fixtures/yaml/song_yaml.txt
311
337
  - test/includes.rb
312
338
  - test/integration_test.rb
313
339
  - test/kit_builder_test.rb
@@ -1,30 +0,0 @@
1
- Song:
2
- Tempo: 99
3
- Flow:
4
- - Verse: x2
5
- - Chorus: x2
6
- - Verse: x2
7
- - Chorus: x4
8
- - Bridge: x1
9
- - Chorus: x4
10
- Kit:
11
- - bass: bass_mono_8.wav
12
- - hhclosed: hh_closed_mono_8.wav
13
- - hhopen: hh_open_mono_8.wav
14
- - snare: snare_mono_8.wav
15
-
16
- Bridge:
17
- - hhclosed: XX.XXX.XXX.XXX.XXX.XXX.XXX.XXX.X
18
-
19
- Chorus:
20
- - bass: X...X...XXXXXXXXX...X...X...X...
21
- - hhopen: ........X.......X.......X.......
22
- - snare: ...................X...X...X...X
23
- - test/sounds/hh_closed_mono_8.wav: X.X.XXX.X.X.XXX.X.X.XXX.X.X.XXX.
24
- - test/sounds/ride_mono_8.wav: ....X...................X.......
25
-
26
- Verse:
27
- - bass: X...X...X...XX..X...X...XX..X...
28
- - hhclosed: X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.
29
- - hhopen: X...............X..............X
30
- - snare: ..X...X...X...X.X...X...X...X...