beats 2.1.0 → 2.1.1

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.
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