beats 1.3.0 → 2.0.0
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 +1 -1
- data/README.markdown +22 -42
- data/bin/beats +6 -7
- data/lib/beats.rb +2 -1
- data/lib/beats/audioengine.rb +13 -13
- data/lib/beats/beatsrunner.rb +7 -8
- data/lib/beats/kit.rb +12 -156
- data/lib/beats/kit_builder.rb +74 -0
- data/lib/beats/pattern.rb +2 -22
- data/lib/beats/song.rb +5 -55
- data/lib/beats/songoptimizer.rb +3 -3
- data/lib/beats/songparser.rb +25 -46
- data/lib/beats/track.rb +20 -31
- data/lib/beats/transforms/song_swinger.rb +2 -4
- data/lib/wavefile/cachingwriter.rb +2 -2
- data/test/audioengine_test.rb +22 -24
- data/test/audioutils_test.rb +1 -1
- data/test/cachingwriter_test.rb +13 -12
- data/test/fixtures/invalid/leading_bar_line.txt +15 -0
- data/test/fixtures/{valid → invalid}/with_structure.txt +2 -2
- data/test/fixtures/valid/multiple_tracks_same_sound.txt +2 -1
- data/test/fixtures/valid/optimize_pattern_collision.txt +4 -5
- data/test/fixtures/valid/track_with_spaces.txt +13 -0
- data/test/includes.rb +1 -4
- data/test/integration_test.rb +5 -5
- data/test/kit_builder_test.rb +52 -0
- data/test/kit_test.rb +18 -141
- data/test/pattern_test.rb +66 -1
- data/test/song_swinger_test.rb +2 -2
- data/test/song_test.rb +9 -33
- data/test/songoptimizer_test.rb +18 -18
- data/test/songparser_test.rb +20 -10
- data/test/track_test.rb +23 -9
- metadata +26 -31
- data/ext/mkrf_conf.rb +0 -28
- data/test/fixtures/invalid/template.txt +0 -31
- data/test/fixtures/valid/foo.txt +0 -18
- data/test/sounds/bass.wav +0 -0
- data/test/sounds/bass2.wav +0 -0
- data/test/sounds/sine-mono-8bit.wav +0 -0
- data/test/sounds/tone.wav +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 386d1a9b9e83c848b2f9ae9c802f1ce9c717c99a
|
4
|
+
data.tar.gz: 287a1390bed816c4bcf65bb5749af04297d8bfd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd8197163c74429d9e1ba1c5ded23af90f423ac48702e085732c5476e788064f497854d585da512f0babd1fb2ad85119fa21caea591e821105e4133cfa8ab443
|
7
|
+
data.tar.gz: 24f11590f134ee21e801d463c6be28334f009ab2da5294b27aa23af2db7ab27550817a61a6db1c3b699ae821e351f1c892da2f2644252ed2bd8faebe4f908236
|
data/LICENSE
CHANGED
data/README.markdown
CHANGED
@@ -13,13 +13,13 @@ Beats is a command-line drum machine written in pure Ruby. Feed it a song notate
|
|
13
13
|
- snare: roland_tr_909_2.wav
|
14
14
|
- hihat: house_2_5.wav
|
15
15
|
- cowbell: big_beat_5.wav
|
16
|
-
- deep: house_2_2.wav
|
17
|
-
|
16
|
+
- deep: house_2_2.wav
|
17
|
+
|
18
18
|
Verse:
|
19
19
|
- bass: X..X...X..X.....
|
20
20
|
- snare: ....X.......X...
|
21
21
|
- hihat: ..X...X...X...X.
|
22
|
-
|
22
|
+
|
23
23
|
Chorus:
|
24
24
|
- bass: X..X...X..X.....
|
25
25
|
- snare: ....X.......X...
|
@@ -29,48 +29,13 @@ Beats is a command-line drum machine written in pure Ruby. Feed it a song notate
|
|
29
29
|
|
30
30
|
And [here's what it sounds like](http://beatsdrummachine.com/media/beat.mp3) after getting the Beats treatment. What a glorious groove!
|
31
31
|
|
32
|
-
|
33
|
-
Current Status
|
34
|
-
--------------
|
35
|
-
|
36
|
-
The latest stable version of Beats is 1.3.0, released on March 4, 2013.
|
37
|
-
|
38
|
-
This release is for all you swingers out there. A new `Swing` declaration in the song header will cause the song to be played with either a swung 8th note or swung 16th note rhythm. For example, take this song:
|
39
|
-
|
40
|
-
Song:
|
41
|
-
Tempo: 120
|
42
|
-
Flow:
|
43
|
-
- Verse: x4
|
44
|
-
|
45
|
-
Verse:
|
46
|
-
- bass.wav: X...X...X...X...
|
47
|
-
- snare.wav: ....X.......X...
|
48
|
-
- hihat.wav: X.X.X.X.X.X.X.X.
|
49
|
-
|
50
|
-
It will [sound like this](http://beatsdrummachine.com/media/straight.wav).
|
51
|
-
|
52
|
-
You can add an 8th note swing like this (notice the 3rd line, everything else is the same):
|
53
|
-
|
54
|
-
Song:
|
55
|
-
Tempo: 120
|
56
|
-
Swing: 8 # Or, 16
|
57
|
-
Flow:
|
58
|
-
- Verse: x4
|
59
|
-
|
60
|
-
Verse:
|
61
|
-
- bass.wav: X...X...X...X...
|
62
|
-
- snare.wav: ....X.......X...
|
63
|
-
- hihat.wav: X.X.X.X.X.X.X.X.
|
64
|
-
|
65
|
-
And it will now [sound like this](http://beatsdrummachine.com/media/swing.wav).
|
66
|
-
|
67
|
-
This release also includes a small bug fix. When you run the `beats` command with no arguments, it now displays the help screen, rather than an error message.
|
32
|
+
For more, check out [beatsdrummachine.com](http://beatsdrummachine.com)
|
68
33
|
|
69
34
|
|
70
35
|
Installation
|
71
36
|
------------
|
72
37
|
|
73
|
-
To install the latest stable version (
|
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:
|
74
39
|
|
75
40
|
gem install beats
|
76
41
|
|
@@ -86,7 +51,22 @@ Usage
|
|
86
51
|
|
87
52
|
Beats runs from the command-line. Run `beats -h` to see the available options. For more detailed instructions, visit [https://github.com/jstrait/beats/wiki/Usage](https://github.com/jstrait/beats/wiki/Usage) on the [Beats Wiki](https://github.com/jstrait/beats/wiki).
|
88
53
|
|
89
|
-
|
54
|
+
Check out [this tutorial at beatsdrummachine.com](http://beatsdrummachine.com/tutorial/) to see an example of how to create a beat from sratch.
|
55
|
+
|
56
|
+
|
57
|
+
What's New
|
58
|
+
----------
|
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.
|
61
|
+
|
62
|
+
* 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
|
+
* Wave files using `WAVEFORMATEXTENSIBLE` format can now be used, due to upgrading the WaveFile gem dependency to v0.8.1 behind the scenes.
|
64
|
+
* Installing the gem is now simpler, since it no longer requires installing the legacy `syck` YAML parser via an extension.
|
65
|
+
* A `Fixnum is deprecated` message is no longer shown when using Ruby 2.4
|
66
|
+
* **Backwards incompatible changes**:
|
67
|
+
* Song files containing a `Structure` section are no longer supported. A `Flow` section should be used instead. Support for the `Structure` section has been deprecated since v1.2.1 (released in 2011).
|
68
|
+
* Track rhythms can no longer start with a `|` character. For example, `|X...X...` is no longer a valid rhythm. However, bar lines are still allowed to appear elsewhere in the rhythm. For example, `X...X...|X...X...|` _is_ a valid rhythm. The reason for this change is that a rhythm starting with `|` is parsed as a YAML scalar block now that Beats is using the Psych YAML library behind the scenes. The fact that the old Syck YAML library didn't treat rhythms starting with a `|` as a YAML scalar block appears to have been a bug in Syck?
|
69
|
+
* The minimum supported Ruby version is now 1.9.3, instead of 1.8.7
|
90
70
|
|
91
71
|
|
92
72
|
Local Development
|
@@ -114,5 +94,5 @@ Contact me (Joel Strait) by sending a GitHub message or opening a GitHub issue.
|
|
114
94
|
|
115
95
|
License
|
116
96
|
-------
|
117
|
-
Beats is released under the MIT license.
|
97
|
+
Beats Drum Machine is released under the MIT license.
|
118
98
|
|
data/bin/beats
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
start_time = Time.now
|
4
4
|
|
5
5
|
$:.unshift File.dirname(__FILE__) + "/../lib"
|
6
|
+
gem "wavefile", "=0.8.1"
|
6
7
|
require "optparse"
|
7
8
|
require "yaml"
|
8
|
-
require "syck"
|
9
9
|
require "wavefile"
|
10
10
|
require "beats"
|
11
11
|
require "wavefile/cachingwriter"
|
@@ -13,9 +13,8 @@ require "wavefile/cachingwriter"
|
|
13
13
|
include Beats
|
14
14
|
|
15
15
|
USAGE_INSTRUCTIONS = ""
|
16
|
-
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE)
|
17
16
|
|
18
|
-
def parse_options
|
17
|
+
def parse_options
|
19
18
|
options = {:split => false}
|
20
19
|
|
21
20
|
optparse = OptionParser.new do |opts|
|
@@ -45,9 +44,9 @@ def parse_options()
|
|
45
44
|
end
|
46
45
|
|
47
46
|
USAGE_INSTRUCTIONS << optparse.to_s
|
48
|
-
optparse.parse!
|
47
|
+
optparse.parse!
|
49
48
|
|
50
|
-
|
49
|
+
options
|
51
50
|
end
|
52
51
|
|
53
52
|
def print_error(error, input_file_name)
|
@@ -57,7 +56,7 @@ def print_error(error, input_file_name)
|
|
57
56
|
puts USAGE_INSTRUCTIONS
|
58
57
|
when Errno::ENOENT
|
59
58
|
puts "Song file '#{input_file_name}' not found.\n"
|
60
|
-
when
|
59
|
+
when SongParser::ParseError
|
61
60
|
puts "Song file '#{input_file_name}' has an error:\n"
|
62
61
|
puts " #{error}\n"
|
63
62
|
when StandardError
|
@@ -70,7 +69,7 @@ def print_error(error, input_file_name)
|
|
70
69
|
end
|
71
70
|
|
72
71
|
begin
|
73
|
-
options = parse_options
|
72
|
+
options = parse_options
|
74
73
|
|
75
74
|
if ARGV.empty?
|
76
75
|
puts USAGE_INSTRUCTIONS
|
data/lib/beats.rb
CHANGED
@@ -2,6 +2,7 @@ require 'beats/audioengine'
|
|
2
2
|
require 'beats/audioutils'
|
3
3
|
require 'beats/beatsrunner'
|
4
4
|
require 'beats/kit'
|
5
|
+
require 'beats/kit_builder'
|
5
6
|
require 'beats/pattern'
|
6
7
|
require 'beats/song'
|
7
8
|
require 'beats/songparser'
|
@@ -10,5 +11,5 @@ require 'beats/track'
|
|
10
11
|
require 'beats/transforms/song_swinger'
|
11
12
|
|
12
13
|
module Beats
|
13
|
-
VERSION = "
|
14
|
+
VERSION = "2.0.0"
|
14
15
|
end
|
data/lib/beats/audioengine.rb
CHANGED
@@ -27,7 +27,7 @@ module Beats
|
|
27
27
|
num_tracks_in_song = @song.total_tracks
|
28
28
|
|
29
29
|
# Open output wave file and prepare it for writing sample data.
|
30
|
-
format = WaveFile::Format.new(@kit.num_channels, @kit.bits_per_sample, SAMPLE_RATE)
|
30
|
+
format = WaveFile::Format.new(@kit.num_channels, "pcm_#{@kit.bits_per_sample}".to_sym, SAMPLE_RATE)
|
31
31
|
writer = WaveFile::CachingWriter.new(output_file_name, format)
|
32
32
|
|
33
33
|
# Generate each pattern's sample data, or pull it from cache, and append it to the wave file.
|
@@ -50,7 +50,7 @@ module Beats
|
|
50
50
|
final_overflow_composite = AudioUtils.scale(final_overflow_composite, format.channels, num_tracks_in_song)
|
51
51
|
writer.write(WaveFile::Buffer.new(final_overflow_composite, format))
|
52
52
|
|
53
|
-
writer.close
|
53
|
+
writer.close
|
54
54
|
|
55
55
|
writer.total_duration
|
56
56
|
end
|
@@ -61,27 +61,27 @@ module Beats
|
|
61
61
|
|
62
62
|
# Generates the sample data for a single track, using the specified sound's sample data.
|
63
63
|
def generate_track_sample_data(track, sound)
|
64
|
-
|
65
|
-
if
|
64
|
+
trigger_step_lengths = track.trigger_step_lengths
|
65
|
+
if trigger_step_lengths == [0]
|
66
66
|
return {:primary => [], :overflow => []} # Is this really what should happen? Why throw away overflow?
|
67
67
|
end
|
68
68
|
|
69
69
|
fill_value = (@kit.num_channels == 1) ? 0 : [0, 0]
|
70
70
|
primary_sample_data = [].fill(fill_value, 0, AudioUtils.step_start_sample(track.step_count, @step_sample_length))
|
71
71
|
|
72
|
-
step_index =
|
73
|
-
|
74
|
-
|
72
|
+
step_index = trigger_step_lengths[0]
|
73
|
+
trigger_sample_length = 0
|
74
|
+
trigger_step_lengths[1...(trigger_step_lengths.length)].each do |trigger_step_length|
|
75
75
|
start_sample = AudioUtils.step_start_sample(step_index, @step_sample_length)
|
76
76
|
end_sample = [(start_sample + sound.length), primary_sample_data.length].min
|
77
|
-
|
77
|
+
trigger_sample_length = end_sample - start_sample
|
78
78
|
|
79
|
-
primary_sample_data[start_sample...end_sample] = sound[0...
|
79
|
+
primary_sample_data[start_sample...end_sample] = sound[0...trigger_sample_length]
|
80
80
|
|
81
|
-
step_index +=
|
81
|
+
step_index += trigger_step_length
|
82
82
|
end
|
83
83
|
|
84
|
-
overflow_sample_data = (sound == [] ||
|
84
|
+
overflow_sample_data = (sound == [] || trigger_step_lengths.length == 1) ? [] : sound[trigger_sample_length...(sound.length)]
|
85
85
|
|
86
86
|
{:primary => primary_sample_data, :overflow => overflow_sample_data}
|
87
87
|
end
|
@@ -143,8 +143,8 @@ module Beats
|
|
143
143
|
if pattern_track_names.member?(incoming_track_name)
|
144
144
|
track = pattern.tracks[incoming_track_name]
|
145
145
|
|
146
|
-
if track.
|
147
|
-
intro_length = (pattern.tracks[incoming_track_name].
|
146
|
+
if track.trigger_step_lengths.length > 1
|
147
|
+
intro_length = (pattern.tracks[incoming_track_name].trigger_step_lengths[0] * step_sample_length).floor
|
148
148
|
end_sample = [end_sample, intro_length].min
|
149
149
|
end
|
150
150
|
end
|
data/lib/beats/beatsrunner.rb
CHANGED
@@ -18,15 +18,15 @@ module Beats
|
|
18
18
|
|
19
19
|
def run
|
20
20
|
base_path = @options[:base_path] || File.dirname(@input_file_name)
|
21
|
-
song, kit = SongParser.new
|
21
|
+
song, kit = SongParser.new.parse(base_path, File.read(@input_file_name))
|
22
22
|
|
23
23
|
song = normalize_for_pattern_option(song)
|
24
24
|
songs_to_generate = normalize_for_split_option(song)
|
25
25
|
|
26
|
-
song_optimizer = SongOptimizer.new
|
27
|
-
durations = songs_to_generate.collect do |output_file_name,
|
28
|
-
|
29
|
-
AudioEngine.new(
|
26
|
+
song_optimizer = SongOptimizer.new
|
27
|
+
durations = songs_to_generate.collect do |output_file_name, song_to_generate|
|
28
|
+
optimized_song = song_optimizer.optimize(song_to_generate, OPTIMIZED_PATTERN_LENGTH)
|
29
|
+
AudioEngine.new(optimized_song, kit).write_to_file(output_file_name)
|
30
30
|
end
|
31
31
|
|
32
32
|
{:duration => durations.last}
|
@@ -45,7 +45,7 @@ module Beats
|
|
45
45
|
end
|
46
46
|
|
47
47
|
song.flow = [pattern_name]
|
48
|
-
song.remove_unused_patterns
|
48
|
+
song.remove_unused_patterns
|
49
49
|
end
|
50
50
|
|
51
51
|
song
|
@@ -56,9 +56,8 @@ module Beats
|
|
56
56
|
songs_to_generate = {}
|
57
57
|
|
58
58
|
if @options[:split]
|
59
|
-
split_songs = song.split
|
59
|
+
split_songs = song.split
|
60
60
|
split_songs.each do |track_name, split_song|
|
61
|
-
# TODO: Move building the output file name into its own method?
|
62
61
|
extension = File.extname(@output_file_name)
|
63
62
|
file_name = File.dirname(@output_file_name) + "/" +
|
64
63
|
File.basename(@output_file_name, extension) + "-" + File.basename(track_name, extension) +
|
data/lib/beats/kit.rb
CHANGED
@@ -1,47 +1,17 @@
|
|
1
1
|
module Beats
|
2
|
-
# Raised when trying to load a sound file which can't be found at the path specified
|
3
|
-
class SoundFileNotFoundError < RuntimeError; end
|
4
|
-
|
5
|
-
# Raised when trying to load a sound file which either isn't actually a sound file, or
|
6
|
-
# is in an unsupported format.
|
7
|
-
class InvalidSoundFormatError < RuntimeError; end
|
8
|
-
|
9
|
-
|
10
|
-
# This class provides a repository for the sounds used in a song. Most usefully, it
|
11
|
-
# also handles converting the sounds to a common format. For example, if a song requires
|
12
|
-
# a sound that is mono/8-bit, another that is stereo/8-bit, and another that is
|
13
|
-
# stereo/16-bit, they have to be converted to a common format before they can be used
|
14
|
-
# together. Kit handles this conversion; all sounds retrieved using
|
15
|
-
# get_sample_data() will be in a common format.
|
16
|
-
#
|
17
|
-
# Sounds can only be added at initialization. During initialization, the sample data
|
18
|
-
# for each sound is loaded into memory, and converted to the common format if necessary.
|
19
|
-
# This format is:
|
20
|
-
#
|
21
|
-
# Bits per sample: 16
|
22
|
-
# Sample rate: 44100
|
23
|
-
# Channels: Stereo, unless all of the kit sounds are mono.
|
24
|
-
#
|
25
|
-
# For example if the kit has these sounds:
|
26
|
-
#
|
27
|
-
# my_sound_1.wav: mono, 16-bit
|
28
|
-
# my_sound_2.wav: stereo, 8-bit
|
29
|
-
# my_sound_3.wav: mono, 8-bit
|
30
|
-
#
|
31
|
-
# they will all be converted to stereo/16-bit during initialization.
|
32
2
|
class Kit
|
33
|
-
|
34
|
-
@base_path = base_path
|
35
|
-
@label_mappings = {}
|
36
|
-
@sound_bank = {}
|
37
|
-
@num_channels = 1
|
38
|
-
@bits_per_sample = 16 # Only use 16-bit files as output. Supporting 8-bit output
|
39
|
-
# means extra complication for no real gain (I'm skeptical
|
40
|
-
# anyone would explicitly want 8-bit output instead of 16-bit).
|
3
|
+
class LabelNotFoundError < RuntimeError; end
|
41
4
|
|
42
|
-
|
5
|
+
PLACEHOLDER_TRACK_NAME = 'empty_track_placeholder_name_234hkj32hjk4hjkhds23'
|
6
|
+
|
7
|
+
def initialize(items, num_channels, bits_per_sample)
|
8
|
+
@items = items
|
9
|
+
@num_channels = num_channels
|
10
|
+
@bits_per_sample = bits_per_sample
|
43
11
|
end
|
44
12
|
|
13
|
+
attr_reader :num_channels, :bits_per_sample
|
14
|
+
|
45
15
|
# Returns the sample data for a sound contained in the Kit. If the all sounds in the
|
46
16
|
# kit are mono, then this will be a flat Array of Fixnums between -32768 and 32767.
|
47
17
|
# Otherwise, this will be an Array of Fixnums pairs between -32768 and 32767.
|
@@ -63,125 +33,11 @@ module Beats
|
|
63
33
|
#
|
64
34
|
# Returns the sample data Array for the sound bound to label.
|
65
35
|
def get_sample_data(label)
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
sample_data = @sound_bank[label]
|
71
|
-
|
72
|
-
if sample_data.nil?
|
73
|
-
# TODO: Should we really throw an exception here rather than just returning nil?
|
74
|
-
raise StandardError, "Kit doesn't contain sound '#{label}'."
|
75
|
-
else
|
76
|
-
return sample_data
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def scale!(scale_factor)
|
81
|
-
@sound_bank.each do |label, sample_array|
|
82
|
-
@sound_bank[label] = AudioUtils.scale(sample_array, @num_channels, scale_factor)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns a YAML representation of the Kit. Produces nicer looking output than the default version
|
87
|
-
# of to_yaml().
|
88
|
-
#
|
89
|
-
# indent_space_count - The number of spaces to indent each line in the output (default: 0).
|
90
|
-
#
|
91
|
-
# Returns a String representation of the Kit in YAML format.
|
92
|
-
def to_yaml(indent_space_count = 0)
|
93
|
-
yaml = ""
|
94
|
-
longest_label_mapping_length =
|
95
|
-
@label_mappings.keys.inject(0) do |max_length, name|
|
96
|
-
(name.to_s.length > max_length) ? name.to_s.length : max_length
|
97
|
-
end
|
98
|
-
|
99
|
-
if @label_mappings.length > 0
|
100
|
-
yaml += " " * indent_space_count + "Kit:\n"
|
101
|
-
ljust_amount = longest_label_mapping_length + 1 # The +1 is for the trailing ":"
|
102
|
-
@label_mappings.sort.each do |label, path|
|
103
|
-
yaml += " " * indent_space_count + " - #{(label + ":").ljust(ljust_amount)} #{path}\n"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
yaml
|
108
|
-
end
|
109
|
-
|
110
|
-
attr_reader :base_path, :label_mappings, :bits_per_sample, :num_channels
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def load_sounds(base_path, kit_items)
|
115
|
-
# Set label mappings
|
116
|
-
kit_items.each do |label, sound_file_names|
|
117
|
-
if sound_file_names.class == Array
|
118
|
-
raise StandardError, "Composite sounds aren't allowed (yet...)"
|
119
|
-
end
|
120
|
-
|
121
|
-
unless label == sound_file_names
|
122
|
-
@label_mappings[label] = sound_file_names
|
123
|
-
end
|
36
|
+
unless @items.has_key?(label)
|
37
|
+
raise LabelNotFoundError, "Kit doesn't contain sound '#{label}'."
|
124
38
|
end
|
125
39
|
|
126
|
-
|
127
|
-
sound_buffers = load_raw_sounds(kit_items)
|
128
|
-
|
129
|
-
canonical_format = WaveFile::Format.new(@num_channels, @bits_per_sample, 44100)
|
130
|
-
|
131
|
-
# Convert each sound to a common format
|
132
|
-
sound_buffers.each {|file_name, buffer| sound_buffers[file_name] = buffer.convert(canonical_format) }
|
133
|
-
|
134
|
-
# If necessary, mix component sounds into a composite
|
135
|
-
kit_items.each do |label, sound_file_names|
|
136
|
-
@sound_bank[label] = mixdown(sound_file_names, sound_buffers)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Converts relative paths into absolute paths. Note that this will also handle
|
141
|
-
# expanding ~ on platforms that support that.
|
142
|
-
def make_file_names_absolute(kit_items)
|
143
|
-
kit_items.each do |label, sound_file_names|
|
144
|
-
unless sound_file_names.class == Array
|
145
|
-
sound_file_names = [sound_file_names]
|
146
|
-
end
|
147
|
-
|
148
|
-
sound_file_names.map! {|sound_file_name| File.expand_path(sound_file_name, base_path) }
|
149
|
-
kit_items[label] = sound_file_names
|
150
|
-
end
|
151
|
-
|
152
|
-
kit_items
|
153
|
-
end
|
154
|
-
|
155
|
-
# Load all sound files, bailing if any are invalid
|
156
|
-
def load_raw_sounds(kit_items)
|
157
|
-
raw_sounds = {}
|
158
|
-
kit_items.values.flatten.each do |sound_file_name|
|
159
|
-
begin
|
160
|
-
info = WaveFile::Reader.info(sound_file_name)
|
161
|
-
WaveFile::Reader.new(sound_file_name).each_buffer(info.sample_frame_count) do |buffer|
|
162
|
-
raw_sounds[sound_file_name] = buffer
|
163
|
-
@num_channels = [@num_channels, buffer.channels].max
|
164
|
-
end
|
165
|
-
rescue Errno::ENOENT
|
166
|
-
raise SoundFileNotFoundError, "Sound file #{sound_file_name} not found."
|
167
|
-
rescue StandardError
|
168
|
-
raise InvalidSoundFormatError, "Sound file #{sound_file_name} is either not a sound file, " +
|
169
|
-
"or is in an unsupported format. BEATS can handle 8, 16, 24, or 32-bit PCM *.wav files."
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
raw_sounds
|
174
|
-
end
|
175
|
-
|
176
|
-
def mixdown(sound_file_names, raw_sounds)
|
177
|
-
sample_arrays = []
|
178
|
-
sound_file_names.each do |sound_file_name|
|
179
|
-
sample_arrays << raw_sounds[sound_file_name].samples
|
180
|
-
end
|
181
|
-
|
182
|
-
composited_sample_data = AudioUtils.composite(sample_arrays, @num_channels)
|
183
|
-
|
184
|
-
AudioUtils.scale(composited_sample_data, @num_channels, sound_file_names.length)
|
40
|
+
@items[label]
|
185
41
|
end
|
186
42
|
end
|
187
43
|
end
|