beats 1.2.5 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f2f7351421971cb29e95868abde34acaa5ab6b2
4
- data.tar.gz: ba692d9e94e5cac568eff994493a45b4739ee2ac
3
+ metadata.gz: bc3e2d1d7e7730ec749de9aacb9446ef847cfcbd
4
+ data.tar.gz: 42bff66e1caa2ff6ff503e57aac501ac36c0d445
5
5
  SHA512:
6
- metadata.gz: 23dd31bfc7e19dcf52dcc573e054f36b46f93b4cd5d5aaa9e9077d9a9227082ddf42b4781f9564af1ab54fd70de1758116e8cf94c6189ca1bbf3f7426628b8dc
7
- data.tar.gz: 19d77737f3a74672d0d73267ba673ecebf99c46c8af487d75762ee8956aab9e034d888d39707e1d6ef10cb6bdb3fd42f499b838b6e032290db664a2476ec5a68
6
+ metadata.gz: c5782528094a11cd93d7a49fa944787fd8150066ebc8f72a75253c4fd1326ae106c58fdb337591f815f4bf4cc85b58fdae497660e96c86789d365b833a72b15f
7
+ data.tar.gz: 666d0a00cf0da6f21e7c1476ded37fd97b41db884add895aff0e627a8e0e9bebbd6c8ee6f5f76d4a2f5916e9d9e83c203846082bc9ce0fad78a681000e0c87be
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  == BEATS
2
2
 
3
- # Copyright (c) 2010-13 Joel Strait
3
+ # Copyright (c) 2010-14 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
@@ -33,19 +33,44 @@ And [here's what it sounds like](http://beatsdrummachine.com/media/beat.mp3) aft
33
33
  Current Status
34
34
  --------------
35
35
 
36
- The latest stable version of Beats is 1.2.5, released on December 31, 2013. This release includes these improvements:
36
+ The latest stable version of Beats is 1.3.0, released on March 4, 2013.
37
37
 
38
- * Tracks that start with a `|` no longer cause an error in Ruby 2.0.0 and 2.1.0.
39
- * Additional Wave file formats can now be used as samples, due to upgrading to [WaveFile 0.6.0](http://wavefilegem.com) behind the scenes:
40
- * 24-bit PCM
41
- * 32-bit IEEE Float
42
- * 64-bit IEEE Float
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.
43
68
 
44
69
 
45
70
  Installation
46
71
  ------------
47
72
 
48
- To install the latest stable version (1.2.5) from [rubygems.org](http://rubygems.org/gems/beats), run the following from the command line:
73
+ To install the latest stable version (1.3.0) from [rubygems.org](http://rubygems.org/gems/beats), run the following from the command line:
49
74
 
50
75
  gem install beats
51
76
 
data/bin/beats CHANGED
@@ -71,6 +71,12 @@ end
71
71
 
72
72
  begin
73
73
  options = parse_options()
74
+
75
+ if ARGV.empty?
76
+ puts USAGE_INSTRUCTIONS
77
+ exit
78
+ end
79
+
74
80
  input_file_name = ARGV[0]
75
81
  output_file_name = ARGV[1]
76
82
 
@@ -7,7 +7,8 @@ require 'beats/song'
7
7
  require 'beats/songparser'
8
8
  require 'beats/songoptimizer'
9
9
  require 'beats/track'
10
+ require 'beats/transforms/song_swinger'
10
11
 
11
12
  module Beats
12
- VERSION = "1.2.5"
13
+ VERSION = "1.3.0"
13
14
  end
@@ -52,7 +52,7 @@ module Beats
52
52
  end
53
53
 
54
54
  def tempo=(new_tempo)
55
- unless new_tempo.class == Fixnum && new_tempo > 0
55
+ unless (new_tempo.class == Fixnum || new_tempo.class == Float) && new_tempo > 0
56
56
  raise InvalidTempoError, "Invalid tempo: '#{new_tempo}'. Tempo must be a number greater than 0."
57
57
  end
58
58
 
@@ -36,7 +36,7 @@ module Beats
36
36
  base_path = raw_song_components[:folder]
37
37
  end
38
38
 
39
- song = Song.new()
39
+ song = Song.new
40
40
 
41
41
  # 1.) Set tempo
42
42
  begin
@@ -66,6 +66,15 @@ module Beats
66
66
  set_song_flow(song, raw_song_components[:flow])
67
67
  end
68
68
 
69
+ # 5.) Swing, if swing flag is set
70
+ if raw_song_components[:swing]
71
+ begin
72
+ song = Transforms::SongSwinger.transform(song, raw_song_components[:swing])
73
+ rescue Transforms::InvalidSwingRateError => detail
74
+ raise SongParseError, "#{detail}"
75
+ end
76
+ end
77
+
69
78
  return song, kit
70
79
  end
71
80
 
@@ -104,6 +113,7 @@ module Beats
104
113
  raw_song_components[:flow] = raw_structure
105
114
  end
106
115
 
116
+ raw_song_components[:swing] = raw_song_components[:header]["swing"]
107
117
  raw_song_components[:patterns] = raw_song_components[:full_definition].reject {|k, v| k == "song"}
108
118
 
109
119
  return raw_song_components
@@ -0,0 +1,54 @@
1
+ module Beats
2
+ module Transforms
3
+ class InvalidSwingRateError < RuntimeError; end
4
+
5
+ class SongSwinger
6
+ def self.transform(song, swing_rate)
7
+ validate_swing_rate(swing_rate)
8
+
9
+ song.patterns.values.each do |pattern|
10
+ pattern.tracks.values.each do |track|
11
+ original_rhythm = track.rhythm
12
+
13
+ if swing_rate == 8
14
+ track.rhythm = swing_8(track.rhythm)
15
+ elsif swing_rate == 16
16
+ track.rhythm = swing_16(track.rhythm)
17
+ end
18
+ end
19
+ end
20
+
21
+ song.tempo *= 1.5
22
+
23
+ song
24
+ end
25
+
26
+ private
27
+
28
+ def self.validate_swing_rate(swing_rate)
29
+ if swing_rate != 8 && swing_rate != 16
30
+ raise InvalidSwingRateError, "Invalid swing rate: '#{swing_rate}'. Swing rate must be 8 or 16."
31
+ end
32
+ end
33
+
34
+ def self.swing_8(original_rhythm)
35
+ original_rhythm.chars.each_slice(4).inject("") do |new_rhythm, slice|
36
+ if slice.length == 1
37
+ new_rhythm << "#{slice[0]}."
38
+ else
39
+ new_rhythm << "#{slice[0]}.#{slice[1]}.#{slice[2]}#{slice[3]}"
40
+ end
41
+
42
+ new_rhythm
43
+ end
44
+ end
45
+
46
+ def self.swing_16(original_rhythm)
47
+ original_rhythm.chars.each_slice(2).inject("") do |new_rhythm, slice|
48
+ new_rhythm << "#{slice[0]}.#{slice[1]}"
49
+ new_rhythm
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -27,9 +27,9 @@ class AudioEngineTest < Test::Unit::TestCase
27
27
  def load_fixtures
28
28
  test_engines = {}
29
29
  base_path = File.dirname(__FILE__) + "/.."
30
- song_parser = SongParser.new()
30
+ song_parser = SongParser.new
31
31
 
32
- test_engines[:blank] = AudioEngine.new(Song.new(), Kit.new(base_path, {}))
32
+ test_engines[:blank] = AudioEngine.new(Song.new, Kit.new(base_path, {}))
33
33
 
34
34
  FIXTURES.each do |fixture_name|
35
35
  song, kit = song_parser.parse(base_path, File.read("test/fixtures/valid/#{fixture_name}.txt"))
@@ -40,7 +40,7 @@ class AudioEngineTest < Test::Unit::TestCase
40
40
  end
41
41
 
42
42
  def test_initialize
43
- test_engines = load_fixtures()
43
+ test_engines = load_fixtures
44
44
 
45
45
  assert_equal(5512.5, test_engines[:blank].step_sample_length)
46
46
  assert_equal(6615.0, test_engines[:repeats_not_specified].step_sample_length)
@@ -159,7 +159,7 @@ class AudioEngineTest < Test::Unit::TestCase
159
159
 
160
160
  def helper_generate_track_sample_data(kit, rhythm, step_sample_length, expected_primary, expected_overflow = [])
161
161
  track = Track.new("foo", rhythm)
162
- engine = MockAudioEngine.new(Song.new(), kit)
162
+ engine = MockAudioEngine.new(Song.new, kit)
163
163
  engine.step_sample_length = step_sample_length
164
164
  actual = engine.generate_track_sample_data(track, kit.get_sample_data("S"))
165
165
 
@@ -182,7 +182,7 @@ class AudioEngineTest < Test::Unit::TestCase
182
182
 
183
183
 
184
184
  # Simple case, no overflow (stereo)
185
- engine = MockAudioEngine.new(Song.new(), MONO_KIT)
185
+ engine = MockAudioEngine.new(Song.new, MONO_KIT)
186
186
  engine.step_sample_length = 4
187
187
  primary, overflow = engine.composite_pattern_tracks(no_overflow_pattern)
188
188
  assert_equal([
@@ -196,7 +196,7 @@ class AudioEngineTest < Test::Unit::TestCase
196
196
 
197
197
 
198
198
  # Simple case, no overflow (stereo)
199
- engine = MockAudioEngine.new(Song.new(), STEREO_KIT)
199
+ engine = MockAudioEngine.new(Song.new, STEREO_KIT)
200
200
  engine.step_sample_length = 4
201
201
  primary, overflow = engine.composite_pattern_tracks(no_overflow_pattern)
202
202
  assert_equal([
@@ -222,7 +222,7 @@ class AudioEngineTest < Test::Unit::TestCase
222
222
 
223
223
 
224
224
  # Some overflow (mono)
225
- engine = MockAudioEngine.new(Song.new(), MONO_KIT)
225
+ engine = MockAudioEngine.new(Song.new, MONO_KIT)
226
226
  engine.step_sample_length = 3
227
227
  primary, overflow = engine.composite_pattern_tracks(overflow_pattern)
228
228
  assert_equal([
@@ -236,7 +236,7 @@ class AudioEngineTest < Test::Unit::TestCase
236
236
 
237
237
 
238
238
  # Some overflow (stereo)
239
- engine = MockAudioEngine.new(Song.new(), STEREO_KIT)
239
+ engine = MockAudioEngine.new(Song.new, STEREO_KIT)
240
240
  engine.step_sample_length = 3
241
241
  primary, overflow = engine.composite_pattern_tracks(overflow_pattern)
242
242
  assert_equal([
@@ -39,5 +39,8 @@ class AudioUtilsTest < Test::Unit::TestCase
39
39
  assert_equal(6615.0, AudioUtils.step_sample_length(44100, 100))
40
40
  assert_equal(3307.5, AudioUtils.step_sample_length(44100, 200))
41
41
  assert_equal(3307.5, AudioUtils.step_sample_length(22050, 100))
42
+
43
+ assert_equal(6874.612880831729, AudioUtils.step_sample_length(44100, 96.2236))
44
+ assert_equal(3437.3064404158645, AudioUtils.step_sample_length(22050, 96.2236))
42
45
  end
43
46
  end
@@ -0,0 +1,9 @@
1
+ # Invalid song, since the swing rate is invalid
2
+ Song:
3
+ Tempo: 100
4
+ Swing: 7 # Invalid swing rate, not 8 or 16
5
+ Flow:
6
+ - Verse: x2
7
+
8
+ Verse:
9
+ - test/sounds/bass_mono_8.wav: X...X...
@@ -0,0 +1,9 @@
1
+ # Invalid song, since the swing rate is invalid
2
+ Song:
3
+ Tempo: 100
4
+ Swing: abc # Invalid swing rate, not 8 or 16
5
+ Flow:
6
+ - Verse: x2
7
+
8
+ Verse:
9
+ - test/sounds/bass_mono_8.wav: X...X...
@@ -0,0 +1,18 @@
1
+ # An example song
2
+
3
+ Song:
4
+ Tempo: 120
5
+ Swing: 16
6
+ Kit:
7
+ - bass: test/sounds/bass_mono_8.wav
8
+ - snare: test/sounds/snare_mono_8.wav
9
+ Flow:
10
+ - Verse: x2
11
+ - Chorus: x2
12
+
13
+ Verse:
14
+ - bass: X...X...
15
+ - snare: ..X...X.
16
+ Chorus:
17
+ - bass: XXXXXXXX
18
+ - snare: .X.X.X.X
@@ -0,0 +1,18 @@
1
+ # An example song
2
+
3
+ Song:
4
+ Tempo: 120
5
+ Swing: 8
6
+ Kit:
7
+ - bass: test/sounds/bass_mono_8.wav
8
+ - snare: test/sounds/snare_mono_8.wav
9
+ Flow:
10
+ - Verse: x2
11
+ - Chorus: x2
12
+
13
+ Verse:
14
+ - bass: X...X...
15
+ - snare: ..X...X.
16
+ Chorus:
17
+ - bass: XXXXXXXX
18
+ - snare: .X.X.X.X
@@ -0,0 +1,17 @@
1
+ # An example song
2
+
3
+ Song:
4
+ Tempo: 120
5
+ Kit:
6
+ - bass: test/sounds/bass_mono_8.wav
7
+ - snare: test/sounds/snare_mono_8.wav
8
+ Flow:
9
+ - Verse: x2
10
+ - Chorus: x2
11
+
12
+ Verse:
13
+ - bass: X...X...
14
+ - snare: ..X...X.
15
+ Chorus:
16
+ - bass: XXXXXXXX
17
+ - snare: .X.X.X.X
@@ -0,0 +1,17 @@
1
+ # An example song
2
+
3
+ Song:
4
+ Tempo: 95.764
5
+ Kit:
6
+ - bass: test/sounds/bass_mono_8.wav
7
+ - snare: test/sounds/snare_mono_8.wav
8
+ Flow:
9
+ - Verse: x2
10
+ - Chorus: x2
11
+
12
+ Verse:
13
+ - bass: X...X...
14
+ - snare: ..X...X.
15
+ Chorus:
16
+ - bass: XXXXXXXX
17
+ - snare: .X.X.X.X
@@ -6,7 +6,7 @@ class IntegrationTest < Test::Unit::TestCase
6
6
 
7
7
  def setup
8
8
  # Make sure no output from previous tests is still around
9
- clean_output_folder()
9
+ clean_output_folder
10
10
  end
11
11
 
12
12
  def test_bad_song_errors
@@ -21,7 +21,7 @@ class IntegrationTest < Test::Unit::TestCase
21
21
  invalid_fixtures.each do |fixture_name|
22
22
  assert_raise(SongParseError) do
23
23
  beats = BeatsRunner.new("test/fixtures/invalid/#{fixture_name}", "doesn't matter", {:split => false})
24
- beats.run()
24
+ beats.run
25
25
  end
26
26
  end
27
27
  end
@@ -56,7 +56,7 @@ class IntegrationTest < Test::Unit::TestCase
56
56
  end
57
57
 
58
58
  beats = BeatsRunner.new(song_fixture, actual_output_file, options)
59
- beats.run()
59
+ beats.run
60
60
  assert(File.exists?(actual_output_file), "Expected file '#{actual_output_file}' to exist, but it doesn't.")
61
61
 
62
62
  # Reading the files this way instead of a plain File.read() for Windows compatibility with binary files
@@ -89,7 +89,7 @@ class IntegrationTest < Test::Unit::TestCase
89
89
  end
90
90
 
91
91
  beats = BeatsRunner.new(song_fixture, actual_output_prefix + ".wav", options)
92
- beats.run()
92
+ beats.run
93
93
  TRACK_NAMES.each do |track_name|
94
94
  if(track_name.start_with?("tom"))
95
95
  track_name += "_#{num_channels}_#{bits_per_sample}"
@@ -99,8 +99,8 @@ class IntegrationTest < Test::Unit::TestCase
99
99
  assert(File.exists?(actual_output_file), "Expected file '#{actual_output_file}' to exist, but it doesn't.")
100
100
 
101
101
  # Reading the files this way instead of a plain File.read() for Windows compatibility with binary files
102
- expected_output_file_contents = File.open(expected_output_file, "rb") {|f| f.read() }
103
- actual_output_file_contents = File.open(actual_output_file, "rb") {|f| f.read() }
102
+ expected_output_file_contents = File.open(expected_output_file, "rb") {|f| f.read }
103
+ actual_output_file_contents = File.open(actual_output_file, "rb") {|f| f.read }
104
104
  assert_equal(expected_output_file_contents, actual_output_file_contents)
105
105
 
106
106
  # Clean up after ourselves
@@ -40,7 +40,7 @@ class KitTest < Test::Unit::TestCase
40
40
  end
41
41
 
42
42
  def test_valid_initialization
43
- kits = generate_test_data()
43
+ kits = generate_test_data
44
44
 
45
45
  assert_equal(16, kits[:empty].bits_per_sample)
46
46
  assert_equal(1, kits[:empty].num_channels)
@@ -93,7 +93,7 @@ class KitTest < Test::Unit::TestCase
93
93
  end
94
94
 
95
95
  def test_get_sample_data
96
- kits = generate_test_data()
96
+ kits = generate_test_data
97
97
  # Should get an error when trying to get a non-existent sound
98
98
  assert_raise(StandardError) { kits[:mono8].get_sample_data("nonexistant") }
99
99
 
@@ -27,7 +27,7 @@ class PatternTest < Test::Unit::TestCase
27
27
  end
28
28
 
29
29
  def test_initialize
30
- test_patterns = generate_test_data()
30
+ test_patterns = generate_test_data
31
31
 
32
32
  pattern = test_patterns[:blank]
33
33
  assert_equal(pattern.name, :blank)
@@ -43,11 +43,11 @@ class PatternTest < Test::Unit::TestCase
43
43
  end
44
44
 
45
45
  def test_step_count
46
- test_patterns = generate_test_data()
46
+ test_patterns = generate_test_data
47
47
 
48
- assert_equal(0, test_patterns[:blank].step_count())
49
- assert_equal(32, test_patterns[:verse].step_count())
50
- assert_equal(4, test_patterns[:staircase].step_count())
48
+ assert_equal(0, test_patterns[:blank].step_count)
49
+ assert_equal(32, test_patterns[:verse].step_count)
50
+ assert_equal(4, test_patterns[:staircase].step_count)
51
51
  end
52
52
 
53
53
  def test_same_tracks_as?
@@ -0,0 +1,170 @@
1
+ require 'includes'
2
+
3
+ class SongSwingerTest < Test::Unit::TestCase
4
+ def test_full_song_swing_rate_8
5
+ base_path = File.dirname(__FILE__) + "/sounds"
6
+ song, kit = SongParser.new.parse(base_path, File.read("test/fixtures/valid/example_mono_16_base_path.txt"))
7
+
8
+ shuffled_song = Transforms::SongSwinger.transform(song, 8)
9
+
10
+ assert_equal(180, shuffled_song.tempo)
11
+ assert_equal([:verse, :verse, :chorus, :chorus, :chorus, :chorus,
12
+ :verse, :verse, :chorus, :chorus, :chorus, :chorus],
13
+ shuffled_song.flow)
14
+
15
+ assert_equal([:chorus, :verse], shuffled_song.patterns.keys.sort)
16
+
17
+ chorus_pattern = shuffled_song.patterns[:chorus]
18
+ assert_equal(["bass",
19
+ "snare",
20
+ "hh_closed",
21
+ "hh_closed2",
22
+ "tom4_mono_16.wav",
23
+ "tom2_mono_16.wav"],
24
+ chorus_pattern.tracks.keys)
25
+
26
+ assert_equal("X.....X.....X.X...X.....", chorus_pattern.tracks["bass"].rhythm)
27
+ assert_equal("......X...........X.....", chorus_pattern.tracks["snare"].rhythm)
28
+ assert_equal("X...XXX...XX............", chorus_pattern.tracks["hh_closed"].rhythm)
29
+ assert_equal("............X...XX....X.", chorus_pattern.tracks["hh_closed2"].rhythm)
30
+ assert_equal(".................X......", chorus_pattern.tracks["tom4_mono_16.wav"].rhythm)
31
+ assert_equal("......................X.", chorus_pattern.tracks["tom2_mono_16.wav"].rhythm)
32
+
33
+ verse_pattern = shuffled_song.patterns[:verse]
34
+ assert_equal(["bass",
35
+ "snare",
36
+ "hh_closed",
37
+ "hh_closed2",
38
+ "agogo"],
39
+ verse_pattern.tracks.keys)
40
+
41
+ assert_equal("X.....X.....X.....X.....", verse_pattern.tracks["bass"].rhythm)
42
+ assert_equal("......................X.", verse_pattern.tracks["snare"].rhythm)
43
+ assert_equal("X...XXX...XX............", verse_pattern.tracks["hh_closed"].rhythm)
44
+ assert_equal("............X...X.X...X.", verse_pattern.tracks["hh_closed2"].rhythm)
45
+ assert_equal("......................XX", verse_pattern.tracks["agogo"].rhythm)
46
+ end
47
+
48
+ def test_full_song_swing_rate_16
49
+ base_path = File.dirname(__FILE__) + "/sounds"
50
+ song, kit = SongParser.new.parse(base_path, File.read("test/fixtures/valid/example_mono_16_base_path.txt"))
51
+
52
+ shuffled_song = Transforms::SongSwinger.transform(song, 16)
53
+
54
+ assert_equal(180, shuffled_song.tempo)
55
+ assert_equal([:verse, :verse, :chorus, :chorus, :chorus, :chorus,
56
+ :verse, :verse, :chorus, :chorus, :chorus, :chorus],
57
+ shuffled_song.flow)
58
+
59
+ assert_equal([:chorus, :verse], shuffled_song.patterns.keys.sort)
60
+
61
+ chorus_pattern = shuffled_song.patterns[:chorus]
62
+ assert_equal(["bass",
63
+ "snare",
64
+ "hh_closed",
65
+ "hh_closed2",
66
+ "tom4_mono_16.wav",
67
+ "tom2_mono_16.wav"],
68
+ chorus_pattern.tracks.keys)
69
+
70
+ assert_equal("X.....X.....X.X...X.....", chorus_pattern.tracks["bass"].rhythm)
71
+ assert_equal("......X...........X.....", chorus_pattern.tracks["snare"].rhythm)
72
+ assert_equal("X..X.XX..X.X............", chorus_pattern.tracks["hh_closed"].rhythm)
73
+ assert_equal("............X..X.X...X..", chorus_pattern.tracks["hh_closed2"].rhythm)
74
+ assert_equal(".................X......", chorus_pattern.tracks["tom4_mono_16.wav"].rhythm)
75
+ assert_equal(".....................X..", chorus_pattern.tracks["tom2_mono_16.wav"].rhythm)
76
+
77
+ verse_pattern = shuffled_song.patterns[:verse]
78
+ assert_equal(["bass",
79
+ "snare",
80
+ "hh_closed",
81
+ "hh_closed2",
82
+ "agogo"],
83
+ verse_pattern.tracks.keys)
84
+
85
+ assert_equal("X.....X.....X.....X.....", verse_pattern.tracks["bass"].rhythm)
86
+ assert_equal(".....................X..", verse_pattern.tracks["snare"].rhythm)
87
+ assert_equal("X..X.XX..X.X............", verse_pattern.tracks["hh_closed"].rhythm)
88
+ assert_equal("............X..X..X..X..", verse_pattern.tracks["hh_closed2"].rhythm)
89
+ assert_equal(".....................X.X", verse_pattern.tracks["agogo"].rhythm)
90
+ end
91
+
92
+ def test_swing_8_rhythm_conversions
93
+ test_rhythm_conversions(8, [["XXXXX.X.", "X.X.XXX...X."],
94
+ ["XXXXXXX", "X.X.XXX.X.X"],
95
+ ["XXXX", "X.X.XX"],
96
+ ["....", "......"],
97
+ ["XXX", "X.X.X"],
98
+ ["XX", "X.X."],
99
+ ["X", "X."]])
100
+ end
101
+
102
+ def test_swing_16_rhythm_conversions
103
+ test_rhythm_conversions(16, [["XXX..X..", "X.XX....X..."],
104
+ ["X..X..X", "X....X...X."],
105
+ ["XX", "X.X"],
106
+ ["X.", "X.."],
107
+ [".X", "..X"],
108
+ ["..", "..."],
109
+ ["X", "X."],
110
+ [".", ".."]])
111
+ end
112
+
113
+ def test_tempo_change
114
+ [8, 16].each do |swing_rate|
115
+ song = Song.new
116
+ song.tempo = 140
117
+
118
+ song = Transforms::SongSwinger.transform(song, swing_rate)
119
+ assert_equal(210, song.tempo)
120
+ end
121
+ end
122
+
123
+ def test_conversion_to_fractional_tempo
124
+ [8, 16].each do |swing_rate|
125
+ song = Song.new
126
+ song.tempo = 145
127
+
128
+ song = Transforms::SongSwinger.transform(song, swing_rate)
129
+ assert_equal(217.5, song.tempo)
130
+ end
131
+ end
132
+
133
+ def test_fractional_tempo
134
+ [8, 16].each do |swing_rate|
135
+ song = Song.new
136
+ song.tempo = 145.325
137
+
138
+ song = Transforms::SongSwinger.transform(song, swing_rate)
139
+ assert_equal(217.98749999999998, song.tempo)
140
+ end
141
+ end
142
+
143
+ def test_invalid_swing_rate
144
+ [7, "abc", "8a", nil, "", [8]].each do |invalid_swing_rate|
145
+ song = Song.new
146
+ song.tempo = 100
147
+
148
+ assert_raise(Transforms::InvalidSwingRateError) do
149
+ song = Transforms::SongSwinger.transform(song, invalid_swing_rate)
150
+ end
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def test_rhythm_conversions(swing_rate, expectations)
157
+ expectations.each do |original_rhythm, expected_rhythm|
158
+ song = Song.new
159
+
160
+ pattern = song.pattern(:my_pattern)
161
+ pattern.track("track1", original_rhythm)
162
+
163
+ song.pattern(pattern)
164
+
165
+ swung_song = Transforms::SongSwinger.transform(song, swing_rate)
166
+ swung_pattern = swung_song.patterns[:my_pattern]
167
+ assert_equal(expected_rhythm, swung_pattern.tracks["track1"].rhythm)
168
+ end
169
+ end
170
+ end
@@ -10,20 +10,20 @@ class SongTest < Test::Unit::TestCase
10
10
  test_songs = {}
11
11
  base_path = File.dirname(__FILE__) + "/.."
12
12
 
13
- test_songs[:blank] = Song.new()
13
+ test_songs[:blank] = Song.new
14
14
 
15
- test_songs[:no_flow] = Song.new()
15
+ test_songs[:no_flow] = Song.new
16
16
  verse = test_songs[:no_flow].pattern :verse
17
17
  verse.track "bass.wav", "X.......X......."
18
18
  verse.track "snare.wav", "....X.......X..."
19
19
  verse.track "hh_closed.wav", "X.X.X.X.X.X.X.X."
20
20
 
21
- song_parser = SongParser.new()
21
+ song_parser = SongParser.new
22
22
  FIXTURES.each do |fixture_name|
23
23
  test_songs[fixture_name], throwaway_kit = song_parser.parse(base_path, File.read("test/fixtures/valid/#{fixture_name}.txt"))
24
24
  end
25
25
 
26
- test_songs[:from_code] = Song.new()
26
+ test_songs[:from_code] = Song.new
27
27
  verse = test_songs[:from_code].pattern :verse
28
28
  verse.track "bass.wav", "X.......X......."
29
29
  verse.track "snare.wav", "....X.......X..."
@@ -38,15 +38,41 @@ class SongTest < Test::Unit::TestCase
38
38
  end
39
39
 
40
40
  def test_initialize
41
- test_songs = generate_test_data()
41
+ test_songs = generate_test_data
42
42
 
43
43
  assert_equal([], test_songs[:blank].flow)
44
44
  assert_equal([], test_songs[:no_flow].flow)
45
45
  assert_equal([:verse, :chorus, :verse, :chorus, :chorus], test_songs[:from_code].flow)
46
46
  end
47
47
 
48
+ def test_tempo=
49
+ song = Song.new
50
+
51
+ song.tempo = 150
52
+ assert_equal(150, song.tempo)
53
+
54
+ song.tempo = 145.854
55
+ assert_equal(145.854, song.tempo)
56
+
57
+ assert_raise(InvalidTempoError) do
58
+ song.tempo = -1
59
+ end
60
+
61
+ assert_raise(InvalidTempoError) do
62
+ song.tempo = -1.0
63
+ end
64
+
65
+ assert_raise(InvalidTempoError) do
66
+ song.tempo = "abc"
67
+ end
68
+
69
+ assert_raise(InvalidTempoError) do
70
+ song.tempo = "150"
71
+ end
72
+ end
73
+
48
74
  def test_pattern
49
- song = Song.new()
75
+ song = Song.new
50
76
  verse1 = song.pattern :Verse
51
77
 
52
78
  assert_equal(:Verse, verse1.name)
@@ -63,7 +89,7 @@ class SongTest < Test::Unit::TestCase
63
89
  end
64
90
 
65
91
  def test_total_tracks
66
- test_songs = generate_test_data()
92
+ test_songs = generate_test_data
67
93
 
68
94
  assert_equal(0, test_songs[:blank].total_tracks)
69
95
  assert_equal(3, test_songs[:no_flow].total_tracks)
@@ -75,7 +101,7 @@ class SongTest < Test::Unit::TestCase
75
101
  end
76
102
 
77
103
  def test_track_names
78
- test_songs = generate_test_data()
104
+ test_songs = generate_test_data
79
105
 
80
106
  assert_equal([], test_songs[:blank].track_names)
81
107
  assert_equal(["bass.wav", "hh_closed.wav", "snare.wav"], test_songs[:no_flow].track_names)
@@ -98,9 +124,9 @@ class SongTest < Test::Unit::TestCase
98
124
  end
99
125
 
100
126
  def test_copy_ignoring_patterns_and_flow
101
- test_songs = generate_test_data()
127
+ test_songs = generate_test_data
102
128
  original_song = test_songs[:example_no_kit]
103
- cloned_song = original_song.copy_ignoring_patterns_and_flow()
129
+ cloned_song = original_song.copy_ignoring_patterns_and_flow
104
130
 
105
131
  assert_not_equal(cloned_song, original_song)
106
132
  assert_equal(cloned_song.tempo, original_song.tempo)
@@ -109,8 +135,8 @@ class SongTest < Test::Unit::TestCase
109
135
  end
110
136
 
111
137
  def test_split
112
- test_songs = generate_test_data()
113
- split_songs = test_songs[:example_with_kit].split()
138
+ test_songs = generate_test_data
139
+ split_songs = test_songs[:example_with_kit].split
114
140
 
115
141
  assert_equal(Hash, split_songs.class)
116
142
  assert_equal(6, split_songs.length)
@@ -141,20 +167,20 @@ class SongTest < Test::Unit::TestCase
141
167
  end
142
168
 
143
169
  def test_remove_unused_patterns
144
- test_songs = generate_test_data()
170
+ test_songs = generate_test_data
145
171
 
146
172
  assert_equal(1, test_songs[:no_flow].patterns.length)
147
- test_songs[:no_flow].remove_unused_patterns()
173
+ test_songs[:no_flow].remove_unused_patterns
148
174
  assert_equal({}, test_songs[:no_flow].patterns)
149
175
 
150
176
  assert_equal(3, test_songs[:example_no_kit].patterns.length)
151
- test_songs[:example_no_kit].remove_unused_patterns()
177
+ test_songs[:example_no_kit].remove_unused_patterns
152
178
  assert_equal(3, test_songs[:example_no_kit].patterns.length)
153
179
  assert_equal(Hash, test_songs[:example_no_kit].patterns.class)
154
180
  end
155
181
 
156
182
  def test_to_yaml
157
- test_songs = generate_test_data()
183
+ test_songs = generate_test_data
158
184
  kit = Kit.new("test/sounds", {"bass" => "bass_mono_8.wav",
159
185
  "snare" => "snare_mono_8.wav",
160
186
  "hhclosed" => "hh_closed_mono_8.wav",
@@ -50,14 +50,14 @@ Verse:
50
50
  - snare: ..........X."
51
51
 
52
52
  def self.load_fixture(fixture_name)
53
- SongParser.new().parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
53
+ SongParser.new.parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
54
54
  end
55
55
 
56
56
  def test_optimize
57
- parser = SongParser.new()
57
+ parser = SongParser.new
58
58
  original_song, kit = parser.parse(File.dirname(__FILE__) + "/..", EXAMPLE_SONG_YAML)
59
59
 
60
- optimizer = SongOptimizer.new()
60
+ optimizer = SongOptimizer.new
61
61
  optimized_song = optimizer.optimize(original_song, 4)
62
62
 
63
63
  assert_equal(optimized_song.tempo, 135)
@@ -122,11 +122,11 @@ Verse:
122
122
  :chorus_0, :chorus_4, :chorus_8, :chorus_12])
123
123
  end
124
124
 
125
- def test_optimize_song_nondivisible_max_pattern_length()
126
- parser = SongParser.new()
125
+ def test_optimize_song_nondivisible_max_pattern_length
126
+ parser = SongParser.new
127
127
  original_song, kit = parser.parse(File.dirname(__FILE__) + "/..", EXAMPLE_SONG_YAML_EMPTY_SUB_PATTERN)
128
128
 
129
- optimizer = SongOptimizer.new()
129
+ optimizer = SongOptimizer.new
130
130
  optimized_song = optimizer.optimize(original_song, 7)
131
131
 
132
132
  pattern = optimized_song.patterns[:verse_0]
@@ -143,17 +143,17 @@ Verse:
143
143
 
144
144
  def test_pattern_collision
145
145
  original_song, kit = SongOptimizerTest.load_fixture("valid/optimize_pattern_collision.txt")
146
- optimizer = SongOptimizer.new()
146
+ optimizer = SongOptimizer.new
147
147
  optimized_song = optimizer.optimize(original_song, 4)
148
148
 
149
149
  assert_equal([:verse2_0, :verse_0, :verse_20], optimized_song.patterns.keys.sort {|x, y| x.to_s <=> y.to_s })
150
150
  end
151
151
 
152
- def test_optimize_song_containing_empty_pattern()
153
- parser = SongParser.new()
152
+ def test_optimize_song_containing_empty_pattern
153
+ parser = SongParser.new
154
154
  original_song, kit = parser.parse(File.dirname(__FILE__) + "/..", EXAMPLE_SONG_YAML_EMPTY_SUB_PATTERN)
155
155
 
156
- optimizer = SongOptimizer.new()
156
+ optimizer = SongOptimizer.new
157
157
  optimized_song = optimizer.optimize(original_song, 4)
158
158
 
159
159
  pattern = optimized_song.patterns[:verse_0]
@@ -5,17 +5,23 @@ class SongParserTest < Test::Unit::TestCase
5
5
 
6
6
  # TODO: Add fixture for track with no rhythm
7
7
  VALID_FIXTURES = [:no_tempo,
8
+ :fractional_tempo,
8
9
  :repeats_not_specified,
9
10
  :pattern_with_overflow,
10
11
  :example_no_kit,
11
12
  :example_with_kit,
12
13
  :example_with_empty_track,
13
14
  :multiple_tracks_same_sound,
14
- :with_structure]
15
+ :with_structure,
16
+ :example_swung_8th,
17
+ :example_swung_16th,
18
+ :example_unswung]
15
19
 
16
20
  INVALID_FIXTURES = [:bad_repeat_count,
17
21
  :bad_flow,
18
22
  :bad_tempo,
23
+ :bad_swing_rate_1,
24
+ :bad_swing_rate_2,
19
25
  :no_header,
20
26
  :no_flow,
21
27
  :pattern_with_no_tracks,
@@ -25,7 +31,7 @@ class SongParserTest < Test::Unit::TestCase
25
31
  :sound_in_track_wrong_format]
26
32
 
27
33
  def self.load_fixture(fixture_name)
28
- SongParser.new().parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
34
+ SongParser.new.parse(FIXTURE_BASE_PATH, File.read("test/fixtures/#{fixture_name}"))
29
35
  end
30
36
 
31
37
  def self.generate_test_data
@@ -43,11 +49,14 @@ class SongParserTest < Test::Unit::TestCase
43
49
 
44
50
  # TODO: Add somes tests to validate the Kits
45
51
  def test_valid_parse
46
- test_songs, test_kits = SongParserTest.generate_test_data()
52
+ test_songs, test_kits = SongParserTest.generate_test_data
47
53
 
48
54
  assert_equal(120, test_songs[:no_tempo].tempo)
49
55
  assert_equal([:verse], test_songs[:no_tempo].flow)
50
56
 
57
+ assert_equal(95.764, test_songs[:fractional_tempo].tempo)
58
+ assert_equal([:verse, :verse, :chorus, :chorus], test_songs[:fractional_tempo].flow)
59
+
51
60
  assert_equal(100, test_songs[:repeats_not_specified].tempo)
52
61
  assert_equal([:verse], test_songs[:repeats_not_specified].flow)
53
62
 
@@ -93,6 +102,39 @@ class SongParserTest < Test::Unit::TestCase
93
102
  assert_equal(1, song.patterns.length)
94
103
  assert_equal(1, song.patterns[:verse].tracks.length)
95
104
  assert_equal("X...X...", song.patterns[:verse].tracks["test/sounds/bass_mono_8.wav"].rhythm)
105
+
106
+ song = test_songs[:example_swung_8th]
107
+ assert_equal(180, song.tempo)
108
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
109
+ assert_equal(2, song.patterns.length)
110
+ assert_equal(2, song.patterns[:verse].tracks.length)
111
+ assert_equal("X.....X.....", song.patterns[:verse].tracks["bass"].rhythm)
112
+ assert_equal("....X.....X.", song.patterns[:verse].tracks["snare"].rhythm)
113
+ assert_equal(2, song.patterns[:chorus].tracks.length)
114
+ assert_equal("X.X.XXX.X.XX", song.patterns[:chorus].tracks["bass"].rhythm)
115
+ assert_equal("..X..X..X..X", song.patterns[:chorus].tracks["snare"].rhythm)
116
+
117
+ song = test_songs[:example_swung_16th]
118
+ assert_equal(180, song.tempo)
119
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
120
+ assert_equal(2, song.patterns.length)
121
+ assert_equal(2, song.patterns[:verse].tracks.length)
122
+ assert_equal("X.....X.....", song.patterns[:verse].tracks["bass"].rhythm)
123
+ assert_equal("...X.....X..", song.patterns[:verse].tracks["snare"].rhythm)
124
+ assert_equal(2, song.patterns[:chorus].tracks.length)
125
+ assert_equal("X.XX.XX.XX.X", song.patterns[:chorus].tracks["bass"].rhythm)
126
+ assert_equal("..X..X..X..X", song.patterns[:chorus].tracks["snare"].rhythm)
127
+
128
+ song = test_songs[:example_unswung]
129
+ assert_equal(120, song.tempo)
130
+ assert_equal([:verse, :verse, :chorus, :chorus], song.flow)
131
+ assert_equal(2, song.patterns.length)
132
+ assert_equal(2, song.patterns[:verse].tracks.length)
133
+ assert_equal("X...X...", song.patterns[:verse].tracks["bass"].rhythm)
134
+ assert_equal("..X...X.", song.patterns[:verse].tracks["snare"].rhythm)
135
+ assert_equal(2, song.patterns[:chorus].tracks.length)
136
+ assert_equal("XXXXXXXX", song.patterns[:chorus].tracks["bass"].rhythm)
137
+ assert_equal(".X.X.X.X", song.patterns[:chorus].tracks["snare"].rhythm)
96
138
  end
97
139
 
98
140
  def test_invalid_parse
@@ -15,7 +15,7 @@ class TrackTest < Test::Unit::TestCase
15
15
  end
16
16
 
17
17
  def test_initialize
18
- test_tracks = generate_test_data()
18
+ test_tracks = generate_test_data
19
19
 
20
20
  assert_equal([0], test_tracks[:blank].beats)
21
21
  assert_equal("bass", test_tracks[:blank].name)
@@ -40,13 +40,13 @@ class TrackTest < Test::Unit::TestCase
40
40
  end
41
41
 
42
42
  def test_step_count
43
- test_tracks = generate_test_data()
44
-
45
- assert_equal(0, test_tracks[:blank].step_count())
46
- assert_equal(1, test_tracks[:solo].step_count())
47
- assert_equal(4, test_tracks[:with_overflow].step_count())
48
- assert_equal(8, test_tracks[:with_barlines].step_count())
49
- assert_equal(4, test_tracks[:placeholder].step_count())
50
- assert_equal(32, test_tracks[:complicated].step_count())
43
+ test_tracks = generate_test_data
44
+
45
+ assert_equal(0, test_tracks[:blank].step_count)
46
+ assert_equal(1, test_tracks[:solo].step_count)
47
+ assert_equal(4, test_tracks[:with_overflow].step_count)
48
+ assert_equal(8, test_tracks[:with_barlines].step_count)
49
+ assert_equal(4, test_tracks[:placeholder].step_count)
50
+ assert_equal(32, test_tracks[:complicated].step_count)
51
51
  end
52
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Strait
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-31 00:00:00.000000000 Z
11
+ date: 2014-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: wavefile
@@ -36,6 +36,9 @@ files:
36
36
  - LICENSE
37
37
  - README.markdown
38
38
  - Rakefile
39
+ - bin/beats
40
+ - ext/mkrf_conf.rb
41
+ - lib/beats.rb
39
42
  - lib/beats/audioengine.rb
40
43
  - lib/beats/audioutils.rb
41
44
  - lib/beats/beatsrunner.rb
@@ -45,9 +48,8 @@ files:
45
48
  - lib/beats/songoptimizer.rb
46
49
  - lib/beats/songparser.rb
47
50
  - lib/beats/track.rb
48
- - lib/beats.rb
51
+ - lib/beats/transforms/song_swinger.rb
49
52
  - lib/wavefile/cachingwriter.rb
50
- - bin/beats
51
53
  - test/audioengine_test.rb
52
54
  - test/audioutils_test.rb
53
55
  - test/cachingwriter_test.rb
@@ -86,6 +88,8 @@ files:
86
88
  - test/fixtures/invalid/bad_flow.txt
87
89
  - test/fixtures/invalid/bad_repeat_count.txt
88
90
  - test/fixtures/invalid/bad_rhythm.txt
91
+ - test/fixtures/invalid/bad_swing_rate_1.txt
92
+ - test/fixtures/invalid/bad_swing_rate_2.txt
89
93
  - test/fixtures/invalid/bad_tempo.txt
90
94
  - test/fixtures/invalid/no_flow.txt
91
95
  - test/fixtures/invalid/no_header.txt
@@ -101,9 +105,13 @@ files:
101
105
  - test/fixtures/valid/example_no_kit.txt
102
106
  - test/fixtures/valid/example_stereo_16.txt
103
107
  - test/fixtures/valid/example_stereo_8.txt
108
+ - test/fixtures/valid/example_swung_16th.txt
109
+ - test/fixtures/valid/example_swung_8th.txt
110
+ - test/fixtures/valid/example_unswung.txt
104
111
  - test/fixtures/valid/example_with_empty_track.txt
105
112
  - test/fixtures/valid/example_with_kit.txt
106
113
  - test/fixtures/valid/foo.txt
114
+ - test/fixtures/valid/fractional_tempo.txt
107
115
  - test/fixtures/valid/multiple_tracks_same_sound.txt
108
116
  - test/fixtures/valid/no_tempo.txt
109
117
  - test/fixtures/valid/optimize_pattern_collision.txt
@@ -115,6 +123,7 @@ files:
115
123
  - test/integration_test.rb
116
124
  - test/kit_test.rb
117
125
  - test/pattern_test.rb
126
+ - test/song_swinger_test.rb
118
127
  - test/song_test.rb
119
128
  - test/songoptimizer_test.rb
120
129
  - test/songparser_test.rb
@@ -207,7 +216,6 @@ files:
207
216
  - test/sounds/tom4_stereo_8.wav
208
217
  - test/sounds/tone.wav
209
218
  - test/track_test.rb
210
- - ext/mkrf_conf.rb
211
219
  homepage: http://beatsdrummachine.com/
212
220
  licenses: []
213
221
  metadata: {}
@@ -227,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
235
  version: '0'
228
236
  requirements: []
229
237
  rubyforge_project:
230
- rubygems_version: 2.1.11
238
+ rubygems_version: 2.2.2
231
239
  signing_key:
232
240
  specification_version: 4
233
241
  summary: A command-line drum machine. Feed it a song notated in YAML, and it will
@@ -271,6 +279,8 @@ test_files:
271
279
  - test/fixtures/invalid/bad_flow.txt
272
280
  - test/fixtures/invalid/bad_repeat_count.txt
273
281
  - test/fixtures/invalid/bad_rhythm.txt
282
+ - test/fixtures/invalid/bad_swing_rate_1.txt
283
+ - test/fixtures/invalid/bad_swing_rate_2.txt
274
284
  - test/fixtures/invalid/bad_tempo.txt
275
285
  - test/fixtures/invalid/no_flow.txt
276
286
  - test/fixtures/invalid/no_header.txt
@@ -286,9 +296,13 @@ test_files:
286
296
  - test/fixtures/valid/example_no_kit.txt
287
297
  - test/fixtures/valid/example_stereo_16.txt
288
298
  - test/fixtures/valid/example_stereo_8.txt
299
+ - test/fixtures/valid/example_swung_16th.txt
300
+ - test/fixtures/valid/example_swung_8th.txt
301
+ - test/fixtures/valid/example_unswung.txt
289
302
  - test/fixtures/valid/example_with_empty_track.txt
290
303
  - test/fixtures/valid/example_with_kit.txt
291
304
  - test/fixtures/valid/foo.txt
305
+ - test/fixtures/valid/fractional_tempo.txt
292
306
  - test/fixtures/valid/multiple_tracks_same_sound.txt
293
307
  - test/fixtures/valid/no_tempo.txt
294
308
  - test/fixtures/valid/optimize_pattern_collision.txt
@@ -300,6 +314,7 @@ test_files:
300
314
  - test/integration_test.rb
301
315
  - test/kit_test.rb
302
316
  - test/pattern_test.rb
317
+ - test/song_swinger_test.rb
303
318
  - test/song_test.rb
304
319
  - test/songoptimizer_test.rb
305
320
  - test/songparser_test.rb