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