beats 1.2.5 → 1.3.0

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