beats 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +21 -23
- data/README.markdown +7 -15
- data/Rakefile +3 -3
- data/bin/beats +5 -5
- data/lib/beats.rb +15 -15
- data/lib/beats/kit.rb +1 -1
- data/lib/beats/kit_builder.rb +5 -1
- data/lib/beats/song.rb +1 -1
- data/lib/beats/song_parser.rb +45 -23
- data/test/audio_engine_test.rb +1 -1
- data/test/audio_utils_test.rb +1 -1
- data/test/caching_writer_test.rb +1 -1
- data/test/fixtures/invalid/flow_invalid_repeat_count_prefix.txt +8 -0
- data/test/fixtures/invalid/flow_invalid_repeat_count_suffix.txt +8 -0
- data/test/fixtures/invalid/{bad_repeat_count.txt → flow_missing_repeat_count.txt} +3 -3
- data/test/fixtures/invalid/flow_negative_repeat_count.txt +8 -0
- data/test/fixtures/invalid/{bad_flow.txt → flow_non_existent_pattern.txt} +0 -0
- data/test/fixtures/invalid/flow_non_string_repeat_count.txt +8 -0
- data/test/fixtures/invalid/flow_not_an_array.txt +15 -0
- data/test/fixtures/invalid/flow_pattern_name_not_a_string.txt +15 -0
- data/test/fixtures/invalid/flow_repeat_count_is_missing_prefix.txt +8 -0
- data/test/fixtures/invalid/flow_section_not_a_string_or_hash.txt +15 -0
- data/test/fixtures/invalid/kit_filename_not_a_string.txt +17 -0
- data/test/fixtures/invalid/kit_not_an_array.txt +17 -0
- data/test/fixtures/invalid/pattern_not_an_array.txt +17 -0
- data/test/fixtures/invalid/pattern_referenced_pattern_name_not_a_string.txt +19 -0
- data/test/fixtures/invalid/pattern_unreferenced_pattern_name_not_a_string.txt +18 -0
- data/test/fixtures/invalid/pattern_with_incomplete_track.txt +10 -0
- data/test/fixtures/invalid/pattern_with_nil_track.txt +10 -0
- data/test/fixtures/valid/composite_sounds_trailing_comma.txt +16 -0
- data/test/fixtures/valid/example_alternate_array_syntax.txt +11 -0
- data/test/fixtures/valid/{multiple_song_header_sections.txt → multiple_copies_of_song_components.txt} +28 -4
- data/test/fixtures/valid/multiple_yaml_documents.txt +33 -0
- data/test/fixtures/valid/track_sound_has_mixed_capitalization.txt +13 -0
- data/test/includes.rb +2 -2
- data/test/integration_test.rb +3 -3
- data/test/kit_builder_test.rb +3 -3
- data/test/kit_test.rb +7 -7
- data/test/pattern_test.rb +176 -176
- data/test/song_optimizer_test.rb +1 -1
- data/test/song_parser_test.rb +86 -11
- data/test/song_swinger_test.rb +1 -1
- data/test/song_test.rb +1 -1
- data/test/sounds/MiXeD_CaPiTaLiZaTiOn.wav +0 -0
- data/test/sounds/bass_mono_16_11025.wav +0 -0
- data/test/sounds/bass_mono_16_22050.wav +0 -0
- data/test/sounds/bass_mono_24.wav +0 -0
- data/test/sounds/bass_mono_8.mp3 +0 -0
- data/test/track_test.rb +1 -1
- metadata +58 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 387b94eb7731f5bf659134c0e6ceb9d0ba01947e130a64a18517fde233a8a958
|
4
|
+
data.tar.gz: c2b0b0b67d07f471861b5b457b522739f0cdc68f6240e66ee999e935633ebfd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe882750e5035fb61aed261c9d7b7e61f3d048da16ee09aaeca089c07aba44abdc15ccceec8f0cc7b7885ddd1c997f3eb19d6abc46d64891aac48996a396c35b
|
7
|
+
data.tar.gz: 5f9a9ffcc09a015fd8bdd00a6308be9af14029924ba9d65967b0fa6d5b9a37c3545155b3ad51f79d8328770310788e82cafd49bb4314272686b7f0dbc9504240
|
data/LICENSE
CHANGED
@@ -1,24 +1,22 @@
|
|
1
|
-
|
1
|
+
Copyright (c) 2010-19 Joel Strait
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
-
# OTHER DEALINGS IN THE SOFTWARE.
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
CHANGED
@@ -35,7 +35,7 @@ For more, check out [beatsdrummachine.com](https://beatsdrummachine.com)
|
|
35
35
|
Installation
|
36
36
|
------------
|
37
37
|
|
38
|
-
To install the latest stable version (2.1.
|
38
|
+
To install the latest stable version (2.1.2) from [rubygems.org](https://rubygems.org/gems/beats), run the following from the command line:
|
39
39
|
|
40
40
|
gem install beats
|
41
41
|
|
@@ -49,25 +49,17 @@ Beats is not very useful unless you have some sounds to use with it. You can dow
|
|
49
49
|
Usage
|
50
50
|
-----
|
51
51
|
|
52
|
-
Beats runs from the command-line. Run `beats -h` to see the available options. For more detailed instructions, visit
|
52
|
+
Beats runs from the command-line. Run `beats -h` to see the available options. For more detailed instructions, visit <https://beatsdrummachine.com/usage/>.
|
53
53
|
|
54
|
-
Check out [this tutorial at beatsdrummachine.com](https://beatsdrummachine.com/tutorial/) to see an example of how to create a beat from
|
54
|
+
Check out [this tutorial at beatsdrummachine.com](https://beatsdrummachine.com/tutorial/) to see an example of how to create a beat from scratch.
|
55
55
|
|
56
56
|
|
57
|
-
What's New in v2.1.
|
57
|
+
What's New in v2.1.2
|
58
58
|
--------------------
|
59
59
|
|
60
|
-
The latest version of Beats is 2.1.
|
60
|
+
The latest version of Beats is 2.1.2, released on December 18, 2019. It contains these changes:
|
61
61
|
|
62
|
-
* Several error
|
63
|
-
* **Bug fix**: Songs can now use *.wav files with more than 2 channels. Previously, using a sound with more than 2 channels would cause a fatal `Invalid sample data array in AudioUtils.normalize()` error.
|
64
|
-
* **Bug fix**: If a sound is defined multiple times in a Kit, the final definition should be used as the winner. However, previously this did not occur if the earlier definition was for a composite sound. For example, with this Kit:
|
65
|
-
|
66
|
-
Kit:
|
67
|
-
- sound: [sound1.wav, sound2.wav]
|
68
|
-
- sound: sound3.wav
|
69
|
-
|
70
|
-
`sound` will now be bound to `sound3.wav`, not `[sound1.wav, sound2.wav]`.
|
62
|
+
* Several confusing/unhelpful errors shown due to an error in an input file have been improved. For example, if a pattern has the invalid name "4", the error message will now be `Pattern name '4' is not valid. It must be a value that will be parsed from YAML as a String.`, instead of `undefined method 'downcase' for 4:Integer`.
|
71
63
|
|
72
64
|
For info about previous releases, visit https://github.com/jstrait/beats/releases.
|
73
65
|
|
@@ -92,7 +84,7 @@ To run the tests:
|
|
92
84
|
Found a Bug? Have a Suggestion? Want to Contribute?
|
93
85
|
---------------------------------------------------
|
94
86
|
|
95
|
-
Contact me (Joel Strait) by
|
87
|
+
Contact me (Joel Strait) by opening a GitHub issue.
|
96
88
|
|
97
89
|
|
98
90
|
License
|
data/Rakefile
CHANGED
data/bin/beats
CHANGED
@@ -15,24 +15,24 @@ def parse_options
|
|
15
15
|
optparse = OptionParser.new do |opts|
|
16
16
|
opts.banner = "usage: beats [options] input_file [output_file]"
|
17
17
|
|
18
|
-
opts.on(
|
18
|
+
opts.on("-s", "--split", "Save each track to an individual wave file") do
|
19
19
|
options[:split] = true
|
20
20
|
end
|
21
21
|
|
22
|
-
opts.on(
|
22
|
+
opts.on("-p", "--pattern PATTERN_NAME", "Output a single pattern instead of the whole song") do |p|
|
23
23
|
options[:pattern] = p
|
24
24
|
end
|
25
25
|
|
26
|
-
opts.on(
|
26
|
+
opts.on("--path BASE_PATH", "The base path used to load sound files with relative paths.") do |base_path|
|
27
27
|
options[:base_path] = base_path
|
28
28
|
end
|
29
29
|
|
30
|
-
opts.on(
|
30
|
+
opts.on("-v", "--version", "Display version number and exit") do
|
31
31
|
puts "Beats Drum Machine #{Beats::VERSION}"
|
32
32
|
exit
|
33
33
|
end
|
34
34
|
|
35
|
-
opts.on(
|
35
|
+
opts.on("-h", "--help", "Display this screen and exit") do
|
36
36
|
puts opts
|
37
37
|
exit
|
38
38
|
end
|
data/lib/beats.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
require
|
1
|
+
require "yaml"
|
2
2
|
|
3
3
|
gem "wavefile", "=0.8.1"
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "wavefile"
|
5
|
+
require "wavefile/caching_writer"
|
6
6
|
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
7
|
+
require "beats/audio_engine"
|
8
|
+
require "beats/audio_utils"
|
9
|
+
require "beats/beats_runner"
|
10
|
+
require "beats/kit"
|
11
|
+
require "beats/kit_builder"
|
12
|
+
require "beats/pattern"
|
13
|
+
require "beats/song"
|
14
|
+
require "beats/song_parser"
|
15
|
+
require "beats/song_optimizer"
|
16
|
+
require "beats/track"
|
17
|
+
require "beats/transforms/song_swinger"
|
18
18
|
|
19
19
|
module Beats
|
20
|
-
VERSION = "2.1.
|
20
|
+
VERSION = "2.1.2"
|
21
21
|
end
|
data/lib/beats/kit.rb
CHANGED
@@ -2,7 +2,7 @@ module Beats
|
|
2
2
|
class Kit
|
3
3
|
class LabelNotFoundError < RuntimeError; end
|
4
4
|
|
5
|
-
PLACEHOLDER_TRACK_NAME =
|
5
|
+
PLACEHOLDER_TRACK_NAME = "empty_track_placeholder_name_234hkj32hjk4hjkhds23"
|
6
6
|
|
7
7
|
def initialize(items, num_channels, bits_per_sample)
|
8
8
|
@items = items
|
data/lib/beats/kit_builder.rb
CHANGED
@@ -27,13 +27,17 @@ module Beats
|
|
27
27
|
|
28
28
|
filenames.each do |filename|
|
29
29
|
unless filename.is_a?(String)
|
30
|
-
raise SoundFileNotFoundError, "
|
30
|
+
raise SoundFileNotFoundError, "Kit sound '#{label}' contains an invalid filename: '#{filename}'. It must be a value that will be parsed from YAML as a String."
|
31
31
|
end
|
32
32
|
composite_replacement = "#{label}-#{File.basename(filename, ".*")}"
|
33
33
|
@labels_to_filenames[composite_replacement] = absolute_file_name(filename)
|
34
34
|
@composite_replacements[label] << composite_replacement
|
35
35
|
end
|
36
36
|
else
|
37
|
+
unless filenames.is_a?(String)
|
38
|
+
raise SoundFileNotFoundError, "Kit sound '#{label}' has an invalid filename: '#{filenames}'. It must be a value that will be parsed from YAML as a String."
|
39
|
+
end
|
40
|
+
|
37
41
|
@labels_to_filenames[label] = absolute_file_name(filenames)
|
38
42
|
@composite_replacements.delete(label)
|
39
43
|
end
|
data/lib/beats/song.rb
CHANGED
@@ -50,7 +50,7 @@ module Beats
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def tempo=(new_tempo)
|
53
|
-
unless (new_tempo.is_a?(Integer) || new_tempo.
|
53
|
+
unless (new_tempo.is_a?(Integer) || new_tempo.is_a?(Float)) && new_tempo > 0
|
54
54
|
raise InvalidTempoError, "Invalid tempo: '#{new_tempo}'. Tempo must be a number greater than 0."
|
55
55
|
end
|
56
56
|
|
data/lib/beats/song_parser.rb
CHANGED
@@ -86,11 +86,9 @@ Song:
|
|
86
86
|
raw_song_definition = YAML.load(raw_yaml_string)
|
87
87
|
rescue Psych::SyntaxError => detail
|
88
88
|
raise ParseError, "Syntax error in YAML file: #{detail}"
|
89
|
-
rescue ArgumentError => detail
|
90
|
-
raise ParseError, "Syntax error in YAML file: #{detail}"
|
91
89
|
end
|
92
90
|
|
93
|
-
header_keys = raw_song_definition.keys.select {|key| key.downcase == "song" }
|
91
|
+
header_keys = raw_song_definition.keys.select {|key| key.is_a?(String) && key.downcase == "song" }
|
94
92
|
|
95
93
|
if header_keys.empty?
|
96
94
|
raise ParseError, NO_SONG_HEADER_ERROR_MSG
|
@@ -115,6 +113,10 @@ Song:
|
|
115
113
|
def self.add_kit_sounds_from_kit(kit_builder, raw_kit)
|
116
114
|
return if raw_kit.nil?
|
117
115
|
|
116
|
+
unless raw_kit.is_a?(Array)
|
117
|
+
raise ParseError, "Kit is not an array. Make sure each sound in the Kit is placed on new indented line prefixed with a '-'"
|
118
|
+
end
|
119
|
+
|
118
120
|
# Add sounds defined in the Kit section of the song header
|
119
121
|
# Converts [{a=>1}, {b=>2}, {c=>3}] from raw YAML to {a=>1, b=>2, c=>3}
|
120
122
|
raw_kit.each do |kit_item|
|
@@ -137,14 +139,26 @@ Song:
|
|
137
139
|
|
138
140
|
def self.add_patterns_to_song(song, kit_builder, raw_patterns)
|
139
141
|
raw_patterns.each do |pattern_name, raw_tracks|
|
142
|
+
if !pattern_name.is_a?(String)
|
143
|
+
raise ParseError, "Pattern name '#{pattern_name}' is not valid. It must be a value that will be parsed from YAML as a String."
|
144
|
+
end
|
145
|
+
|
140
146
|
if raw_tracks.nil?
|
141
147
|
# TODO: Possibly allow if pattern not referenced in the Flow, or has 0 repeats?
|
142
148
|
raise ParseError, "Pattern '#{pattern_name}' has no tracks. It needs at least one."
|
143
149
|
end
|
144
150
|
|
151
|
+
if !raw_tracks.is_a?(Array)
|
152
|
+
raise ParseError, "Tracks in pattern '#{pattern_name}' are not an Array. Make sure each track is placed on new indented line prefixed with a '-'"
|
153
|
+
end
|
154
|
+
|
145
155
|
tracks = []
|
146
156
|
|
147
|
-
raw_tracks.
|
157
|
+
raw_tracks.each_with_index do |raw_track, index|
|
158
|
+
if !raw_track.is_a?(Hash)
|
159
|
+
raise ParseError, "Track ##{index + 1} in pattern '#{pattern_name}' is incomplete. Must be in form '- <kit/file name>: <rhythm>'"
|
160
|
+
end
|
161
|
+
|
148
162
|
track_names = raw_track.keys.first
|
149
163
|
rhythm = raw_track.values.first
|
150
164
|
|
@@ -158,7 +172,7 @@ Song:
|
|
158
172
|
|
159
173
|
track_names.map! do |track_name|
|
160
174
|
unless track_name.is_a?(String)
|
161
|
-
raise ParseError, "'#{track_name}' in pattern '#{pattern_name}' is not a valid
|
175
|
+
raise ParseError, "'#{track_name}' in pattern '#{pattern_name}' is not a valid filename/kit sound. It must be a value that will be parsed from YAML as a String."
|
162
176
|
end
|
163
177
|
kit_builder.composite_replacements[track_name] || track_name
|
164
178
|
end
|
@@ -177,34 +191,42 @@ Song:
|
|
177
191
|
def self.set_song_flow(song, raw_flow)
|
178
192
|
flow = []
|
179
193
|
|
194
|
+
if !raw_flow.is_a?(Array)
|
195
|
+
raise ParseError, "Song flow is not an array. Make sure each section of the flow is placed on new indented line prefixed with a '-'"
|
196
|
+
end
|
197
|
+
|
180
198
|
raw_flow.each do |pattern_item|
|
181
|
-
if pattern_item.
|
182
|
-
|
199
|
+
if !pattern_item.is_a?(Hash)
|
200
|
+
if pattern_item.is_a?(String)
|
201
|
+
pattern_item = {pattern_item => "x1"}
|
202
|
+
else
|
203
|
+
raise ParseError, "'#{pattern_item}' is invalid flow section; must be in form '- <pattern name>: <repeat count>'"
|
204
|
+
end
|
183
205
|
end
|
184
206
|
|
185
207
|
pattern_name = pattern_item.keys.first
|
208
|
+
if !pattern_name.is_a?(String)
|
209
|
+
raise ParseError, "Pattern name '#{pattern_name}' in flow is not valid. It must be a value that will be parsed from YAML as a String."
|
210
|
+
end
|
186
211
|
pattern_name_sym = pattern_name.downcase.to_sym
|
187
212
|
|
188
|
-
|
189
|
-
multiples_str = pattern_item[pattern_name]
|
190
|
-
multiples_str.slice!(0)
|
191
|
-
multiples = multiples_str.to_i
|
213
|
+
repeat_count_str = pattern_item[pattern_name]
|
192
214
|
|
193
|
-
unless
|
215
|
+
unless repeat_count_str.is_a?(String) && repeat_count_str.match(/^x[0-9]+$/) != nil
|
194
216
|
raise ParseError,
|
195
|
-
"'#{
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
217
|
+
"'#{repeat_count_str}' is an invalid number of repeats for pattern '#{pattern_name}'. Number of repeats must be a whole number >= 0, prefixed with 'x'."
|
218
|
+
end
|
219
|
+
|
220
|
+
repeat_count = repeat_count_str[1..-1].to_i
|
221
|
+
|
222
|
+
if repeat_count > 0 && !song.patterns.has_key?(pattern_name_sym)
|
223
|
+
# This test is purposefully designed to only throw an error if the number of repeats is greater
|
224
|
+
# than 0. This allows you to specify an undefined pattern in the flow with "x0" repeats.
|
225
|
+
# This can be convenient for defining the flow before all patterns have been added to the song file.
|
226
|
+
raise ParseError, "Song flow includes non-existent pattern: '#{pattern_name}'"
|
205
227
|
end
|
206
228
|
|
207
|
-
|
229
|
+
repeat_count.times { flow << pattern_name_sym }
|
208
230
|
end
|
209
231
|
|
210
232
|
song.flow = flow
|
data/test/audio_engine_test.rb
CHANGED
data/test/audio_utils_test.rb
CHANGED
data/test/caching_writer_test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Invalid song, since the number of repeats for pattern Verse is
|
1
|
+
# Invalid song, since the number of repeats for pattern Verse is missing
|
2
2
|
Song:
|
3
3
|
Tempo: 100
|
4
4
|
Flow:
|
5
|
-
- Verse:
|
5
|
+
- Verse:
|
6
6
|
|
7
7
|
Verse:
|
8
|
-
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X...
|
8
|
+
- test/sounds/bass_mono_8.wav: X...X...X...XX..X...X...XX..X...
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Invalid song, since the flow is not an array (i.e., each line is not prefixed with "-")
|
2
|
+
Song:
|
3
|
+
Tempo: 100
|
4
|
+
Flow:
|
5
|
+
Verse: x2
|
6
|
+
Chorus: x2
|
7
|
+
Verse: x2
|
8
|
+
Chorus: x2
|
9
|
+
|
10
|
+
|
11
|
+
Verse:
|
12
|
+
- test/sounds/bass_mono_8.wav: X...X...
|
13
|
+
|
14
|
+
Chorus:
|
15
|
+
- test/sounds/snare_mono_8.wav: X...X...
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Invalid song, a pattern used in the flow has a name which will not be parsed as a String
|
2
|
+
Song:
|
3
|
+
Tempo: 120
|
4
|
+
Flow:
|
5
|
+
- Verse: x2
|
6
|
+
- 4: x2 # Invalid pattern name!
|
7
|
+
Kit:
|
8
|
+
- bass: ../../sounds/bass_mono_8.wav
|
9
|
+
- snare: ../../sounds/snare_mono_8.wav
|
10
|
+
- hh_closed: ../../sounds/hh_closed_mono_8.wav
|
11
|
+
- agogo: ../../sounds/agogo_high_mono_8.wav
|
12
|
+
|
13
|
+
Verse:
|
14
|
+
- bass: X...X...X...X...
|
15
|
+
- snare: ..............X.
|