beats 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -2
  3. data/README.markdown +17 -80
  4. data/bin/beats +2 -7
  5. data/lib/beats.rb +12 -6
  6. data/lib/beats/{audioengine.rb → audio_engine.rb} +7 -8
  7. data/lib/beats/{audioutils.rb → audio_utils.rb} +35 -17
  8. data/lib/beats/{beatsrunner.rb → beats_runner.rb} +2 -2
  9. data/lib/beats/kit.rb +6 -5
  10. data/lib/beats/kit_builder.rb +12 -8
  11. data/lib/beats/pattern.rb +7 -16
  12. data/lib/beats/song.rb +7 -8
  13. data/lib/beats/{songoptimizer.rb → song_optimizer.rb} +11 -8
  14. data/lib/beats/{songparser.rb → song_parser.rb} +46 -48
  15. data/lib/beats/track.rb +14 -17
  16. data/lib/beats/transforms/song_swinger.rb +7 -5
  17. data/lib/wavefile/{cachingwriter.rb → caching_writer.rb} +2 -2
  18. data/test/{audioengine_test.rb → audio_engine_test.rb} +49 -46
  19. data/test/audio_utils_test.rb +86 -0
  20. data/test/{cachingwriter_test.rb → caching_writer_test.rb} +0 -0
  21. data/test/fixtures/invalid/sound_in_kit_wrong_format.txt +1 -1
  22. data/test/fixtures/invalid/sound_in_track_wrong_format.txt +1 -1
  23. data/test/fixtures/valid/empty_kit.txt +14 -0
  24. data/test/fixtures/valid/example_song_header_different_capitalization.txt +21 -0
  25. data/test/fixtures/valid/multiple_patterns_same_name.txt +31 -0
  26. data/test/fixtures/valid/multiple_song_header_sections.txt +29 -0
  27. data/test/includes.rb +0 -9
  28. data/test/kit_builder_test.rb +20 -0
  29. data/test/kit_test.rb +7 -0
  30. data/test/pattern_test.rb +86 -74
  31. data/test/{songoptimizer_test.rb → song_optimizer_test.rb} +5 -8
  32. data/test/{songparser_test.rb → song_parser_test.rb} +109 -13
  33. data/test/song_swinger_test.rb +6 -4
  34. data/test/song_test.rb +32 -14
  35. data/test/sounds/agogo_high_stereo_16.wav +0 -0
  36. data/test/sounds/agogo_low_stereo_16.wav +0 -0
  37. data/test/sounds/bass2_stereo_16.wav +0 -0
  38. data/test/sounds/bass_stereo_16.wav +0 -0
  39. data/test/sounds/clave_high_stereo_16.wav +0 -0
  40. data/test/sounds/clave_low_stereo_16.wav +0 -0
  41. data/test/sounds/conga_high_stereo_16.wav +0 -0
  42. data/test/sounds/conga_low_stereo_16.wav +0 -0
  43. data/test/sounds/cowbell_high_stereo_16.wav +0 -0
  44. data/test/sounds/cowbell_low_stereo_16.wav +0 -0
  45. data/test/sounds/hh_closed_stereo_16.wav +0 -0
  46. data/test/sounds/hh_open_stereo_16.wav +0 -0
  47. data/test/sounds/ride_stereo_16.wav +0 -0
  48. data/test/sounds/rim_stereo_16.wav +0 -0
  49. data/test/sounds/snare2_stereo_16.wav +0 -0
  50. data/test/sounds/snare_stereo_16.wav +0 -0
  51. data/test/sounds/tom1_stereo_16.wav +0 -0
  52. data/test/sounds/tom2_stereo_16.wav +0 -0
  53. data/test/sounds/tom3_stereo_16.wav +0 -0
  54. data/test/sounds/tom4_stereo_16.wav +0 -0
  55. data/test/track_test.rb +51 -5
  56. metadata +184 -176
  57. data/test/audioutils_test.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b76857d49851dc03ef574dadcbbc01aff5aaea9f
4
- data.tar.gz: df598d61cfaa1a0ecb8f1bd493dc8b1f0436869e
2
+ SHA256:
3
+ metadata.gz: 8ae934eac074a47b7bb5f587fd96eb457f0ddabfbf18f5667e838dd01832c247
4
+ data.tar.gz: ee54703a136d795679807ea67f4f5cb79532f090d0c641f924590a036a35b2d9
5
5
  SHA512:
6
- metadata.gz: 59bd59d74a085c9d60c0f4584a9a6704c58aa05e4856d24884e4e5de622e578cd9e7a1a47edb35e172b8b1ff2f67c0386b7d99db238dcaf11d3f366a57ec42d7
7
- data.tar.gz: 7ab6c3283977bcda76772ae287e67d2d120fdd8623fe777364dfc70bd6a1250b3c50e96bec6e047d7da199dd6ec6877b02cbf4e0b7d9009a480becdf92498656
6
+ metadata.gz: 2f2e931c783acab297d17187cadd51ebbba9cd5bd55ec9a81a6fddd15b0d95b8b03027508cbed4e3b07740a3e0d04d25e9e000b06074b916bc23cc6e4e0df3ed
7
+ data.tar.gz: f6fe7883c7a9f21d62d252eec3f646aa2af5f47acda24d20dc43c8761fcf84c87716a2cbc53248fdcf31f4c7832cb5d24353ea49520ada5555a6a9cb85234898
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- == BEATS
1
+ == Beats Drum Machine
2
2
 
3
- # Copyright (c) 2010-17 Joel Strait
3
+ # Copyright (c) 2010-18 Joel Strait
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person
6
6
  # obtaining a copy of this software and associated documentation
@@ -27,23 +27,23 @@ Beats is a command-line drum machine written in pure Ruby. Feed it a song notate
27
27
  - cowbell: ....XX.X..X.X...
28
28
  - deep: .............X..
29
29
 
30
- And [here's what it sounds like](http://beatsdrummachine.com/media/beat.mp3) after getting the Beats treatment. What a glorious groove!
30
+ And [here's what it sounds like](https://beatsdrummachine.com/media/beat.mp3) after getting the Beats treatment. What a glorious groove!
31
31
 
32
- For more, check out [beatsdrummachine.com](http://beatsdrummachine.com)
32
+ For more, check out [beatsdrummachine.com](https://beatsdrummachine.com)
33
33
 
34
34
 
35
35
  Installation
36
36
  ------------
37
37
 
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:
38
+ To install the latest stable version (2.1.1) from [rubygems.org](https://rubygems.org/gems/beats), run the following from the command line:
39
39
 
40
40
  gem install beats
41
41
 
42
- Note that if you are installing using the default version of Ruby that comes with MacOS X, you might get a file permission error. If that happens, use `sudo gem install beats` instead. If you are using RVM, plain `gem install beats` should work fine.
42
+ Note: if you're installing using the default version of Ruby that comes with macOS, you might get a file permission error. If that happens, use `sudo gem install beats` instead. If you're using a version manager such as rbenv, chruby, or RVM, plain `gem install beats` should work fine.
43
43
 
44
44
  Once installed, you can then run Beats from the command-line using the `beats` command.
45
45
 
46
- Beats is not very useful unless you have some sounds to use with it. You can download some example sounds from [http://beatsdrummachine.com](http://beatsdrummachine.com/download#drum-kits).
46
+ Beats is not very useful unless you have some sounds to use with it. You can download some example sounds from [https://beatsdrummachine.com](https://beatsdrummachine.com/download#drum-kits).
47
47
 
48
48
 
49
49
  Usage
@@ -51,87 +51,25 @@ Usage
51
51
 
52
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).
53
53
 
54
- Check out [this tutorial at beatsdrummachine.com](http://beatsdrummachine.com/tutorial/) to see an example of how to create a beat from sratch.
54
+ Check out [this tutorial at beatsdrummachine.com](https://beatsdrummachine.com/tutorial/) to see an example of how to create a beat from sratch.
55
55
 
56
56
 
57
- What's New
58
- ----------
57
+ What's New in v2.1.1
58
+ --------------------
59
59
 
60
- The latest version of Beats is 2.1.0, released on September 9, 2017.
60
+ The latest version of Beats is 2.1.1, released on June 29, 2018. It contains these changes:
61
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.
62
+ * Several error messages are improved to be more accurate or specific.
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:
63
65
 
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
- ------------------------
66
+ Kit:
67
+ - sound: [sound1.wav, sound2.wav]
68
+ - sound: sound3.wav
124
69
 
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.
70
+ `sound` will now be bound to `sound3.wav`, not `[sound1.wav, sound2.wav]`.
126
71
 
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...`
128
- * Wave files using `WAVEFORMATEXTENSIBLE` format can now be used, due to upgrading the WaveFile gem dependency to v0.8.1 behind the scenes.
129
- * Installing the gem is now simpler, since it no longer requires installing the legacy `syck` YAML parser via an extension.
130
- * A `Fixnum is deprecated` message is no longer shown when using Ruby 2.4
131
- * **Backwards incompatible changes**:
132
- * 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).
133
- * 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?
134
- * The minimum supported Ruby version is now 1.9.3, instead of 1.8.7
72
+ For info about previous releases, visit https://github.com/jstrait/beats/releases.
135
73
 
136
74
 
137
75
  Local Development
@@ -160,4 +98,3 @@ Contact me (Joel Strait) by sending a GitHub message or opening a GitHub issue.
160
98
  License
161
99
  -------
162
100
  Beats Drum Machine is released under the MIT license.
163
-
data/bin/beats CHANGED
@@ -2,13 +2,8 @@
2
2
 
3
3
  start_time = Time.now
4
4
 
5
- $:.unshift File.dirname(__FILE__) + "/../lib"
6
- gem "wavefile", "=0.8.1"
7
5
  require "optparse"
8
- require "yaml"
9
- require "wavefile"
10
6
  require "beats"
11
- require "wavefile/cachingwriter"
12
7
 
13
8
  include Beats
14
9
 
@@ -79,9 +74,9 @@ begin
79
74
  input_file_name = ARGV[0]
80
75
  output_file_name = ARGV[1]
81
76
 
82
- beats = BeatsRunner.new(input_file_name, output_file_name, options)
77
+ beats_runner = BeatsRunner.new(input_file_name, output_file_name, options)
83
78
 
84
- output = beats.run()
79
+ output = beats_runner.run()
85
80
  duration = output[:duration]
86
81
  puts "#{duration.minutes}:#{duration.seconds.to_s.rjust(2, '0')} of audio written in #{Time.now - start_time} seconds."
87
82
  rescue => error
@@ -1,15 +1,21 @@
1
- require 'beats/audioengine'
2
- require 'beats/audioutils'
3
- require 'beats/beatsrunner'
1
+ require 'yaml'
2
+
3
+ gem "wavefile", "=0.8.1"
4
+ require 'wavefile'
5
+ require 'wavefile/caching_writer'
6
+
7
+ require 'beats/audio_engine'
8
+ require 'beats/audio_utils'
9
+ require 'beats/beats_runner'
4
10
  require 'beats/kit'
5
11
  require 'beats/kit_builder'
6
12
  require 'beats/pattern'
7
13
  require 'beats/song'
8
- require 'beats/songparser'
9
- require 'beats/songoptimizer'
14
+ require 'beats/song_parser'
15
+ require 'beats/song_optimizer'
10
16
  require 'beats/track'
11
17
  require 'beats/transforms/song_swinger'
12
18
 
13
19
  module Beats
14
- VERSION = "2.1.0"
20
+ VERSION = "2.1.1"
15
21
  end
@@ -12,7 +12,6 @@ module Beats
12
12
  #
13
13
  class AudioEngine
14
14
  SAMPLE_RATE = 44100
15
- PACK_CODE = "s*" # All output sample data is assumed to be 16-bit
16
15
 
17
16
  def initialize(song, kit)
18
17
  @song = song
@@ -37,8 +36,8 @@ module Beats
37
36
  unless packed_pattern_cache.member?(key)
38
37
  sample_data = generate_pattern_sample_data(@song.patterns[pattern_name], incoming_overflow)
39
38
 
40
- packed_pattern_cache[key] = { :primary => WaveFile::Buffer.new(sample_data[:primary], format),
41
- :overflow => WaveFile::Buffer.new(sample_data[:overflow], format) }
39
+ packed_pattern_cache[key] = { primary: WaveFile::Buffer.new(sample_data[:primary], format),
40
+ overflow: WaveFile::Buffer.new(sample_data[:overflow], format) }
42
41
  end
43
42
 
44
43
  writer.write(packed_pattern_cache[key][:primary])
@@ -63,10 +62,10 @@ module Beats
63
62
  def generate_track_sample_data(track, sound)
64
63
  trigger_step_lengths = track.trigger_step_lengths
65
64
  if trigger_step_lengths == [0]
66
- return {:primary => [], :overflow => []} # Is this really what should happen? Why throw away overflow?
65
+ return {primary: [], overflow: []} # Is this really what should happen? Why throw away overflow?
67
66
  end
68
67
 
69
- fill_value = (@kit.num_channels == 1) ? 0 : [0, 0]
68
+ fill_value = (@kit.num_channels == 1) ? 0 : Array.new(@kit.num_channels, 0)
70
69
  primary_sample_data = [].fill(fill_value, 0, AudioUtils.step_start_sample(track.step_count, @step_sample_length))
71
70
 
72
71
  step_index = trigger_step_lengths[0]
@@ -83,7 +82,7 @@ module Beats
83
82
 
84
83
  overflow_sample_data = (sound == [] || trigger_step_lengths.length == 1) ? [] : sound[trigger_sample_length...(sound.length)]
85
84
 
86
- {:primary => primary_sample_data, :overflow => overflow_sample_data}
85
+ {primary: primary_sample_data, overflow: overflow_sample_data}
87
86
  end
88
87
 
89
88
  # Composites the sample data for each of the pattern's tracks, and returns the overflow sample data
@@ -93,7 +92,7 @@ module Beats
93
92
  # Unless cached, composite each track's sample data.
94
93
  if @composited_pattern_cache[pattern].nil?
95
94
  primary_sample_data, overflow_sample_data = composite_pattern_tracks(pattern)
96
- @composited_pattern_cache[pattern] = {:primary => primary_sample_data.dup, :overflow => overflow_sample_data.dup}
95
+ @composited_pattern_cache[pattern] = {primary: primary_sample_data.dup, overflow: overflow_sample_data.dup}
97
96
  else
98
97
  primary_sample_data = @composited_pattern_cache[pattern][:primary].dup
99
98
  overflow_sample_data = @composited_pattern_cache[pattern][:overflow].dup
@@ -106,7 +105,7 @@ module Beats
106
105
  overflow_sample_data)
107
106
  primary_sample_data = AudioUtils.scale(primary_sample_data, @kit.num_channels, @song.total_tracks)
108
107
 
109
- {:primary => primary_sample_data, :overflow => overflow_sample_data}
108
+ {primary: primary_sample_data, overflow: overflow_sample_data}
110
109
  end
111
110
 
112
111
  def composite_pattern_tracks(pattern)
@@ -7,6 +7,10 @@ module Beats
7
7
  # of the longest input array.
8
8
  # WARNING: Incoming arrays can be modified.
9
9
  def self.composite(sample_arrays, num_channels)
10
+ if num_channels < 1
11
+ raise ArgumentError, "`num_channels` must be 1 or greater"
12
+ end
13
+
10
14
  if sample_arrays == []
11
15
  return []
12
16
  end
@@ -16,14 +20,26 @@ module Beats
16
20
 
17
21
  composited_output = sample_arrays.slice!(0)
18
22
  sample_arrays.each do |sample_array|
19
- unless sample_array == []
20
- if num_channels == 1
21
- sample_array.length.times {|i| composited_output[i] += sample_array[i] }
22
- elsif num_channels == 2
23
- sample_array.length.times do |i|
24
- composited_output[i] = [composited_output[i][0] + sample_array[i][0],
25
- composited_output[i][1] + sample_array[i][1]]
23
+ if num_channels == 1
24
+ sample_array.length.times {|i| composited_output[i] += sample_array[i] }
25
+ elsif num_channels == 2
26
+ sample_array.length.times do |i|
27
+ # Setting composited_output[i][<channel_index>] won't necessary work,
28
+ # because each sub array might point to the same array, causing more
29
+ # samples to be set than expected. For example, a sample buffer initialized
30
+ # using `[].fill([0, 0], 0, 1000)` will result in an array where each index
31
+ # is a pointer to the same array instance.
32
+ composited_output[i] = [composited_output[i][0] + sample_array[i][0],
33
+ composited_output[i][1] + sample_array[i][1]]
34
+ end
35
+ elsif num_channels > 2
36
+ sample_array.each_with_index do |sub_array, i|
37
+ composited_sub_array = []
38
+ sub_array.each_with_index do |sample, j|
39
+ composited_sub_array << composited_output[i][j] + sample
26
40
  end
41
+
42
+ composited_output[i] = composited_sub_array
27
43
  end
28
44
  end
29
45
  end
@@ -35,21 +51,23 @@ module Beats
35
51
  # Scales the amplitude of the incoming sample array by *scale* amount. Can be used in conjunction
36
52
  # with composite() to make sure composited sample arrays don't have an amplitude greater than 1.0.
37
53
  def self.scale(sample_array, num_channels, scale)
38
- if sample_array == []
54
+ if num_channels < 1
55
+ raise ArgumentError, "`num_channels` must be 1 or greater"
56
+ end
57
+
58
+ if scale == 1 || sample_array == []
39
59
  return sample_array
40
60
  end
41
61
 
42
- if scale > 1
43
- if num_channels == 1
44
- sample_array = sample_array.map {|sample| sample / scale }
45
- elsif num_channels == 2
46
- sample_array = sample_array.map {|sample| [sample[0] / scale, sample[1] / scale]}
47
- else
48
- raise StandardError, "Invalid sample data array in AudioUtils.normalize()"
62
+ if num_channels == 1
63
+ sample_array.map {|sample| sample / scale }
64
+ elsif num_channels == 2
65
+ sample_array.map {|sample_frame| [sample_frame[0] / scale, sample_frame[1] / scale]}
66
+ elsif num_channels > 2
67
+ sample_array.map do |sample_frame|
68
+ sample_frame.map {|sample| sample / scale }
49
69
  end
50
70
  end
51
-
52
- sample_array
53
71
  end
54
72
 
55
73
 
@@ -18,7 +18,7 @@ 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.parse(base_path, File.read(@input_file_name))
21
+ song, kit = SongParser.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)
@@ -29,7 +29,7 @@ module Beats
29
29
  AudioEngine.new(optimized_song, kit).write_to_file(output_file_name)
30
30
  end
31
31
 
32
- {:duration => durations.last}
32
+ {duration: durations.last}
33
33
  end
34
34
 
35
35
  private
@@ -6,15 +6,16 @@ module Beats
6
6
 
7
7
  def initialize(items, num_channels, bits_per_sample)
8
8
  @items = items
9
+ @labels = @items.keys.freeze
9
10
  @num_channels = num_channels
10
11
  @bits_per_sample = bits_per_sample
11
12
  end
12
13
 
13
- attr_reader :num_channels, :bits_per_sample
14
+ attr_reader :labels, :num_channels, :bits_per_sample
14
15
 
15
16
  # Returns the sample data for a sound contained in the Kit. If the all sounds in the
16
- # kit are mono, then this will be a flat Array of Fixnums between -32768 and 32767.
17
- # Otherwise, this will be an Array of Fixnums pairs between -32768 and 32767.
17
+ # kit are mono, then this will be a flat Array of Integers between -32768 and 32767.
18
+ # Otherwise, this will be an Array of Integers pairs between -32768 and 32767.
18
19
  #
19
20
  # label - The name of the sound to get sample data for. If the sound was defined in
20
21
  # the Kit section of a song file, this will generally be a descriptive label
@@ -23,11 +24,11 @@ module Beats
23
24
  #
24
25
  # Examples
25
26
  #
26
- # # If @num_channels is 1, a flat Array of Fixnums:
27
+ # # If @num_channels is 1, a flat Array of Integers:
27
28
  # get_sample_data("bass")
28
29
  # # => [154, 7023, 8132, 2622, -132, 34, ..., -6702]
29
30
  #
30
- # # If @num_channels is 2, a Array of Fixnums pairs:
31
+ # # If @num_channels is 2, a Array of Integers pairs:
31
32
  # get_sample_data("snare")
32
33
  # # => [[57, 1265], [-452, 10543], [-2531, 12643], [-6372, 11653], ..., [5482, 25673]]
33
34
  #
@@ -20,19 +20,22 @@ module Beats
20
20
  def add_item(label, filenames)
21
21
  if filenames.is_a?(Array)
22
22
  if filenames.empty?
23
- raise SoundFileNotFoundError, "Kit sound '#{label}' has an empty composite pattern (i.e. \"[]\"), which is not valid."
23
+ raise SoundFileNotFoundError, "Kit sound '#{label}' is an empty composite sound (i.e. \"[]\"), which is not valid."
24
24
  end
25
25
 
26
+ @composite_replacements[label] = []
27
+
26
28
  filenames.each do |filename|
27
29
  unless filename.is_a?(String)
28
30
  raise SoundFileNotFoundError, "The Kit sound '#{label}' contains an invalid file: '#{filename}'"
29
31
  end
30
- @labels_to_filenames["#{label}-#{File.basename(filename, ".*")}"] = absolute_file_name(filename)
32
+ composite_replacement = "#{label}-#{File.basename(filename, ".*")}"
33
+ @labels_to_filenames[composite_replacement] = absolute_file_name(filename)
34
+ @composite_replacements[label] << composite_replacement
31
35
  end
32
-
33
- @composite_replacements[label] = filenames.map {|filename| "#{label}-#{File.basename(filename, ".*")}" }
34
36
  else
35
37
  @labels_to_filenames[label] = absolute_file_name(filenames)
38
+ @composite_replacements.delete(label)
36
39
  end
37
40
  end
38
41
 
@@ -80,10 +83,11 @@ module Beats
80
83
  sample_buffer = buffer
81
84
  end
82
85
  rescue Errno::ENOENT
83
- raise SoundFileNotFoundError, "Sound file #{filename} not found."
84
- rescue StandardError
85
- raise InvalidSoundFormatError, "Sound file #{filename} is either not a sound file, " +
86
- "or is in an unsupported format. BEATS can handle 8, 16, 24, or 32-bit PCM *.wav files."
86
+ raise SoundFileNotFoundError, "Sound file `#{filename}` not found."
87
+ rescue WaveFile::UnsupportedFormatError
88
+ raise InvalidSoundFormatError, "Sound file `#{filename}` is not a supported *.wav format. Beats can use *.wav files with a sample format of 8/16/24/32 PCM or floating point."
89
+ rescue WaveFile::InvalidFormatError
90
+ raise InvalidSoundFormatError, "Sound file `#{filename}` is either not a sound file, or is in an unsupported format. Beats can use *.wav files with a sample format of 8/16/24/32 PCM or floating point."
87
91
  end
88
92
 
89
93
  sample_buffer
@@ -5,28 +5,19 @@ module Beats
5
5
  # This object is like sheet music; the AudioEngine is responsible creating actual
6
6
  # audio data for a Pattern (with the help of a Kit).
7
7
  class Pattern
8
- def initialize(name)
8
+ def initialize(name, tracks=[])
9
9
  @name = name
10
10
  @tracks = {}
11
- end
12
11
 
13
- # Adds a new track to the pattern.
14
- def track(name, rhythm)
15
- track_key = unique_track_name(name)
16
- new_track = Track.new(name, rhythm)
17
- @tracks[track_key] = new_track
12
+ longest_track_length = tracks.map {|track| track.rhythm.length }.max
18
13
 
19
- # If the new track is longer than any of the previously added tracks,
20
- # pad the other tracks with trailing . to make them all the same length.
21
- # Necessary to prevent incorrect overflow calculations for tracks.
22
- longest_track_length = step_count
23
- @tracks.values.each do |track|
24
- if track.rhythm.length < longest_track_length
25
- track.rhythm += "." * (longest_track_length - track.rhythm.length)
26
- end
14
+ tracks.each do |track|
15
+ track_key = unique_track_name(track.name)
16
+ new_track = Track.new(track.name, track.rhythm.ljust(longest_track_length, Track::REST))
17
+ @tracks[track_key] = new_track
27
18
  end
28
19
 
29
- new_track
20
+ @tracks.freeze
30
21
  end
31
22
 
32
23
  def step_count