mtk 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MTK::Intervals do
3
+ describe MTK::Constants::Intervals do
4
4
 
5
5
  describe 'P1' do
6
6
  it 'is 0 semitones' do
7
- P1.should == 0
7
+ P1.should == Interval[0]
8
8
  end
9
9
  it 'is available via a module property and via mixin' do
10
10
  Intervals::P1.should == P1
@@ -13,7 +13,7 @@ describe MTK::Intervals do
13
13
 
14
14
  describe 'm2' do
15
15
  it 'is 1 semitone' do
16
- m2.should == 1
16
+ m2.should == Interval[1]
17
17
  end
18
18
  it 'is available via a module property and via mixin' do
19
19
  Intervals::P1.should == P1
@@ -22,7 +22,7 @@ describe MTK::Intervals do
22
22
 
23
23
  describe 'M2' do
24
24
  it 'is 2 semitones' do
25
- M2.should == 2
25
+ M2.should == Interval[2]
26
26
  end
27
27
  it 'is available via a module property and via mixin' do
28
28
  Intervals::P1.should == P1
@@ -31,7 +31,7 @@ describe MTK::Intervals do
31
31
 
32
32
  describe 'm3' do
33
33
  it 'is 3 semitones' do
34
- m3.should == 3
34
+ m3.should == Interval[3]
35
35
  end
36
36
  it 'is available via a module property and via mixin' do
37
37
  Intervals::P1.should == P1
@@ -40,7 +40,7 @@ describe MTK::Intervals do
40
40
 
41
41
  describe 'M3' do
42
42
  it 'is 4 semitones' do
43
- M3.should == 4
43
+ M3.should == Interval[4]
44
44
  end
45
45
  it 'is available via a module property and via mixin' do
46
46
  Intervals::P1.should == P1
@@ -49,7 +49,7 @@ describe MTK::Intervals do
49
49
 
50
50
  describe 'P4' do
51
51
  it 'is 5 semitones' do
52
- P4.should == 5
52
+ P4.should == Interval[5]
53
53
  end
54
54
  it 'is available via a module property and via mixin' do
55
55
  Intervals::P1.should == P1
@@ -58,7 +58,7 @@ describe MTK::Intervals do
58
58
 
59
59
  describe 'TT' do
60
60
  it 'is 6 semitones' do
61
- TT.should == 6
61
+ TT.should == Interval[6]
62
62
  end
63
63
  it 'is available via a module property and via mixin' do
64
64
  Intervals::P1.should == P1
@@ -67,7 +67,7 @@ describe MTK::Intervals do
67
67
 
68
68
  describe 'P5' do
69
69
  it 'is 7 semitones' do
70
- P5.should == 7
70
+ P5.should == Interval[7]
71
71
  end
72
72
  it 'is available via a module property and via mixin' do
73
73
  Intervals::P1.should == P1
@@ -76,7 +76,7 @@ describe MTK::Intervals do
76
76
 
77
77
  describe 'm6' do
78
78
  it 'is 8 semitones' do
79
- m6.should == 8
79
+ m6.should == Interval[8]
80
80
  end
81
81
  it 'is available via a module property and via mixin' do
82
82
  Intervals::P1.should == P1
@@ -85,7 +85,7 @@ describe MTK::Intervals do
85
85
 
86
86
  describe 'M6' do
87
87
  it 'is 9 semitones' do
88
- M6.should == 9
88
+ M6.should == Interval[9]
89
89
  end
90
90
  it 'is available via a module property and via mixin' do
91
91
  Intervals::P1.should == P1
@@ -94,7 +94,7 @@ describe MTK::Intervals do
94
94
 
95
95
  describe 'm7' do
96
96
  it 'is 10 semitones' do
97
- m7.should == 10
97
+ m7.should == Interval[10]
98
98
  end
99
99
  it 'is available via a module property and via mixin' do
100
100
  Intervals::P1.should == P1
@@ -103,7 +103,7 @@ describe MTK::Intervals do
103
103
 
104
104
  describe 'M7' do
105
105
  it 'is 11 semitones' do
106
- M7.should == 11
106
+ M7.should == Interval[11]
107
107
  end
108
108
  it 'is available via a module property and via mixin' do
109
109
  Intervals::P1.should == P1
@@ -112,7 +112,7 @@ describe MTK::Intervals do
112
112
 
113
113
  describe 'P8' do
114
114
  it 'is 12 semitones' do
115
- P8.should == 12
115
+ P8.should == Interval[12]
116
116
  end
117
117
  it 'is available via a module property and via mixin' do
118
118
  Intervals::P1.should == P1
@@ -140,22 +140,4 @@ describe MTK::Intervals do
140
140
  end
141
141
  end
142
142
 
143
- describe ".[]" do
144
- it "looks up the constant by name" do
145
- Intervals['P1'].should == P1
146
- Intervals['m2'].should == m2
147
- Intervals['M2'].should == M2
148
- Intervals['m3'].should == m3
149
- Intervals['M3'].should == M3
150
- Intervals['P4'].should == P4
151
- Intervals['TT'].should == TT
152
- Intervals['P5'].should == P5
153
- Intervals['m6'].should == m6
154
- Intervals['M6'].should == M6
155
- Intervals['m7'].should == m7
156
- Intervals['M7'].should == M7
157
- Intervals['P8'].should == P8
158
- end
159
- end
160
-
161
143
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MTK::PitchClasses do
3
+ describe MTK::Constants::PitchClasses do
4
4
  let(:cases) {
5
5
  [
6
6
  [PitchClass['C'], 'C', 0],
@@ -20,9 +20,9 @@ describe MTK::PitchClasses do
20
20
 
21
21
  it "defines constants for the 12 pitch classes in the twelve-tone octave" do
22
22
  cases.length.should == 12
23
- cases.each do |const, name, int_value|
23
+ cases.each do |const, name, value|
24
24
  const.name.should == name
25
- const.to_i.should == int_value
25
+ const.value.should == value
26
26
  end
27
27
  end
28
28
 
@@ -49,10 +49,14 @@ describe MTK::PitchClasses do
49
49
  end
50
50
 
51
51
  describe ".[]" do
52
- it "acts like PitchClas.[]" do
52
+ it "acts like PitchClass.[]" do
53
53
  for name in PitchClasses::PITCH_CLASS_NAMES
54
54
  PitchClasses[name].should == PitchClass[name]
55
55
  end
56
56
  end
57
+
58
+ it "returns nil for arguments it doesn't understand" do
59
+ PitchClasses[:invalid].should be_nil
60
+ end
57
61
  end
58
62
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MTK::Pitches do
3
+ describe MTK::Constants::Pitches do
4
4
 
5
5
  it "defines constants for the 128 notes in MIDI" do
6
6
  Pitches.constants.length.should == 130 # there's also the PITCHES and PITCH_NAMES constants
@@ -47,6 +47,10 @@ describe MTK::Pitches do
47
47
  end
48
48
  end
49
49
  end
50
+
51
+ it "returns nil for arguments it doesn't understand" do
52
+ Pitches[:invalid].should be_nil
53
+ end
50
54
  end
51
55
 
52
56
  end
@@ -0,0 +1,372 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Duration do
4
+
5
+ let(:one_beat) { Duration[1] }
6
+ let(:two_beats) { Duration[2] }
7
+
8
+
9
+ describe 'NAMES' do
10
+ it "is the list of base duration names available" do
11
+ Duration::NAMES.should =~ %w( w h q i s r x )
12
+ end
13
+
14
+ it "is immutable" do
15
+ lambda{ Duration::NAMES << 'z' }.should raise_error
16
+ end
17
+ end
18
+
19
+
20
+ describe 'VALUES_BY_NAME' do
21
+ it 'maps names to values' do
22
+ Duration::VALUES_BY_NAME.each do |name,value|
23
+ Duration.from_s(name).value.should == value
24
+ end
25
+ end
26
+
27
+ it 'is immutable' do
28
+ lambda{ Duration::VALUES_BY_NAME << 'z' }.should raise_error
29
+ end
30
+ end
31
+
32
+
33
+ describe '.new' do
34
+ it "constructs a Duration with whatever value is given" do
35
+ float = 0.5
36
+ value = Duration.new(float).value
37
+ value.should be_equal float
38
+ end
39
+ end
40
+
41
+
42
+ describe '.[]' do
43
+ it "constructs and caches a duration from a Numeric" do
44
+ Duration[1].should be_equal Duration[1]
45
+ end
46
+
47
+ it "retains the new() method's ability to construct uncached objects" do
48
+ Duration.new(1).should_not be_equal Duration[1]
49
+ end
50
+
51
+ it "uses the argument as the value, if the argument is a Fixnum" do
52
+ value = Duration[4].value
53
+ value.should be_equal 4
54
+ end
55
+
56
+ it "converts other Numerics to Rational values" do
57
+ value = Duration[0.5].value
58
+ value.should be_a Rational
59
+ value.should == Rational(1,2)
60
+ end
61
+ end
62
+
63
+
64
+ describe '.from_i' do
65
+ it "uses the argument as the value" do
66
+ value = Duration.from_i(4).value
67
+ value.should be_equal 4
68
+ end
69
+ end
70
+
71
+ describe '.from_f' do
72
+ it "converts Floats to Rational" do
73
+ value = Duration.from_f(0.5).value
74
+ value.should be_a Rational
75
+ value.should == Rational(1,2)
76
+ end
77
+ end
78
+
79
+ describe '.from_s' do
80
+ it "converts any of the duration NAMES into a Duration with the value from the VALUES_BY_NAME mapping" do
81
+ for name in Duration::NAMES
82
+ Duration.from_s(name).value.should == Duration::VALUES_BY_NAME[name]
83
+ end
84
+ end
85
+
86
+ it "converts any of the duration names prefixed with a '-' to the negative value" do
87
+ for name in Duration::NAMES
88
+ Duration.from_s("-#{name}").value.should == -1 * Duration::VALUES_BY_NAME[name]
89
+ end
90
+ end
91
+
92
+ it "converts any of the duration names suffixed a 't' to 2/3 of the value" do
93
+ for name in Duration::NAMES
94
+ Duration.from_s("#{name}t").value.should == Rational(2,3) * Duration::VALUES_BY_NAME[name]
95
+ end
96
+ end
97
+
98
+ it "converts any of the duration names suffixed a '.' to 3/2 of the value" do
99
+ for name in Duration::NAMES
100
+ Duration.from_s("#{name}.").value.should == Rational(3,2) * Duration::VALUES_BY_NAME[name]
101
+ end
102
+ end
103
+
104
+ it "converts suffix combinations of 't' and '.' (multiplying by 2/3 and 3/2 for each)" do
105
+ trip = Rational(2,3)
106
+ dot = Rational(3,2)
107
+ for name in Duration::NAMES
108
+ for suffix,multiplier in {'tt' => trip*trip, 't.' => trip*dot, '..' => dot*dot, 't..t.' => trip*dot*dot*trip*dot}
109
+ Duration.from_s("#{name}#{suffix}").value.should == multiplier * Duration::VALUES_BY_NAME[name]
110
+ end
111
+ end
112
+ end
113
+
114
+ it "parses durations with integer multipliers" do
115
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
116
+ multiplier = index+5
117
+ Duration.from_s("#{multiplier}#{duration}").should == multiplier * Duration(duration)
118
+ end
119
+ end
120
+
121
+ it "parses durations with float multipliers" do
122
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
123
+ multiplier = (index+1)*1.123
124
+ Duration.from_s("#{multiplier}#{duration}").should == multiplier * Duration(duration)
125
+ end
126
+ end
127
+
128
+ it "parses durations with float multipliers" do
129
+ Durations::DURATION_NAMES.each_with_index do |duration, index|
130
+ multiplier = Rational(index+1, index+2)
131
+ Duration.from_s("#{multiplier}#{duration}").should == multiplier * Duration(duration)
132
+ end
133
+ end
134
+
135
+ it "parses combinations of all modifiers" do
136
+ Duration.from_s("-4/5qt.").value.should == -4.0/5 * 1 * 2/3.0 * 3/2.0
137
+ Duration.from_s("-11.234h.tt").value.should == 2 * 3/2.0 * 2/3.0 * 2/3.0 * -11.234
138
+ end
139
+ end
140
+
141
+ describe '.from_name' do
142
+ it "acts like .from_s" do
143
+ for name in Duration::NAMES
144
+ Duration.from_name(name).value.should == Duration::VALUES_BY_NAME[name]
145
+ end
146
+ end
147
+ end
148
+
149
+
150
+ describe '#value' do
151
+ it "is the value used to construct the Duration" do
152
+ Duration.new(1.111).value.should == 1.111
153
+ end
154
+ end
155
+
156
+
157
+ describe '#length' do
158
+ it 'is the value for positive values' do
159
+ Duration.new(4).length.should == 4
160
+ end
161
+
162
+ it 'is the absolute value for negative values' do
163
+ Duration.new(-4).length.should == 4
164
+ end
165
+ end
166
+
167
+
168
+ describe '#rest?' do
169
+ it 'is false for positive values' do
170
+ Duration.new(4).rest?.should be_false
171
+ end
172
+
173
+ it 'is true for negative values' do
174
+ Duration.new(-4).rest?.should be_true
175
+ end
176
+ end
177
+
178
+
179
+ describe '#to_f' do
180
+ it "is the value as a floating point number" do
181
+ f = Duration.new(Rational(1,2)).to_f
182
+ f.should be_a Float
183
+ f.should == 0.5
184
+ end
185
+ end
186
+
187
+ describe '#to_i' do
188
+ it "is the value rounded to the nearest integer" do
189
+ i = Duration.new(0.5).to_i
190
+ i.should be_a Fixnum
191
+ i.should == 1
192
+ Duration.new(0.49).to_i.should == 0
193
+ end
194
+ end
195
+
196
+ describe '#to_s' do
197
+ it "is value.to_s suffixed with 'beats' for beat values > 1" do
198
+ for value in [2, Rational(3,2), 3.25]
199
+ Duration.new(value).to_s.should == value.to_s + ' beats'
200
+ end
201
+ end
202
+
203
+ it "is value.to_s suffixed with 'beats' for positive, non-zero beat values < 1" do
204
+ for value in [Rational(1,2), 0.25]
205
+ Duration.new(value).to_s.should == value.to_s + ' beat'
206
+ end
207
+ end
208
+
209
+ it "is value.to_s suffixed with 'beats' for a value of 0" do
210
+ for value in [0, 0.0, Rational(0,2)]
211
+ Duration.new(value).to_s.should == value.to_s + ' beats'
212
+ end
213
+ end
214
+
215
+ it "is value.to_s suffixed with 'beats' for beat values < -1" do
216
+ for value in [-2, -Rational(3,2), -3.25]
217
+ Duration.new(value).to_s.should == value.to_s + ' beats'
218
+ end
219
+ end
220
+
221
+ it "is value.to_s suffixed with 'beat' for negative, non-zero beat values > -1" do
222
+ for value in [-Rational(1,2), -0.25]
223
+ Duration.new(value).to_s.should == value.to_s + ' beat'
224
+ end
225
+ end
226
+
227
+ it "rounds to 2 decimal places when value.to_s is overly long" do
228
+ Duration.new(Math.sqrt 2).to_s.should == '1.41 beats'
229
+ end
230
+ end
231
+
232
+ describe '#inspect' do
233
+ it 'is "#<MTK::Duration:{object_id} @value={value}>"' do
234
+ for value in [0, 60, 60.5, 127]
235
+ duration = Duration.new(value)
236
+ duration.inspect.should == "#<MTK::Duration:#{duration.object_id} @value=#{value}>"
237
+ end
238
+ end
239
+ end
240
+
241
+ describe '#==' do
242
+ it "compares two duration values for equality" do
243
+ Duration.new(Rational(1,2)).should == Duration.new(0.5)
244
+ Duration.new(4).should == Duration.new(4)
245
+ Duration.new(1.1).should_not == Duration.new(1)
246
+ end
247
+ end
248
+
249
+ describe "#<=>" do
250
+ it "orders durations based on their underlying value" do
251
+ ( Duration.new(1) <=> Duration.new(1.1) ).should < 0
252
+ ( Duration.new(2) <=> Duration.new(1) ).should > 0
253
+ ( Duration.new(1.0) <=> Duration.new(1) ).should == 0
254
+ end
255
+ end
256
+
257
+
258
+
259
+ describe '#+' do
260
+ it 'adds #value to the Numeric argument' do
261
+ (one_beat + 1.5).should == Duration[2.5]
262
+ end
263
+
264
+ it 'works with a Duration argument' do
265
+ (one_beat + Duration[1.5]).should == Duration[2.5]
266
+ end
267
+
268
+ it 'returns a new duration (Duration is immutable)' do
269
+ original = one_beat
270
+ modified = one_beat + 2
271
+ original.should_not == modified
272
+ original.should == Duration[1]
273
+ end
274
+ end
275
+
276
+ describe '#-' do
277
+ it 'subtract the Numeric argument from #value' do
278
+ (one_beat - 0.5).should == Duration[0.5]
279
+ end
280
+
281
+ it 'works with a Duration argument' do
282
+ (one_beat - Duration[0.5]).should == Duration[0.5]
283
+ end
284
+
285
+ it 'returns a new duration (Duration is immutable)' do
286
+ original = one_beat
287
+ modified = one_beat - 0.5
288
+ original.should_not == modified
289
+ original.should == Duration[1]
290
+ end
291
+ end
292
+
293
+
294
+ describe '#*' do
295
+ it 'multiplies #value to the Numeric argument' do
296
+ (two_beats * 3).should == Duration[6]
297
+ end
298
+
299
+ it 'works with a Duration argument' do
300
+ (two_beats * Duration[3]).should == Duration[6]
301
+ end
302
+
303
+ it 'returns a new duration (Duration is immutable)' do
304
+ original = one_beat
305
+ modified = one_beat * 2
306
+ original.should_not == modified
307
+ original.should == Duration[1]
308
+ end
309
+ end
310
+
311
+ describe '#/' do
312
+ it 'divides #value by the Numeric argument' do
313
+ (two_beats / 4.0).should == Duration[0.5]
314
+ end
315
+
316
+ it 'works with a Duration argument' do
317
+ (two_beats / Duration[4]).should == Duration[0.5]
318
+ end
319
+
320
+ it 'returns a new duration (Duration is immutable)' do
321
+ original = one_beat
322
+ modified = one_beat / 2
323
+ original.should_not == modified
324
+ original.should == Duration[1]
325
+ end
326
+ end
327
+
328
+ describe '#coerce' do
329
+ it 'allows a Duration to be added to a Numeric' do
330
+ (2 + one_beat).should == Duration[3]
331
+ end
332
+
333
+ it 'allows a Duration to be subtracted from a Numeric' do
334
+ (7 - two_beats).should == Duration[5]
335
+ end
336
+
337
+ it 'allows a Duration to be multiplied to a Numeric' do
338
+ (3 * two_beats).should == Duration[6]
339
+ end
340
+
341
+ it 'allows a Numeric to be divided by a Duration' do
342
+ (8.0 / two_beats).should == Duration[4]
343
+ end
344
+ end
345
+
346
+ end
347
+
348
+ describe MTK do
349
+
350
+ describe '#Duration' do
351
+ it "acts like .from_s if the argument is a String" do
352
+ Duration('w').should == Duration.from_s('w')
353
+ end
354
+
355
+ it "acts like .from_s if the argument is a Symbol" do
356
+ Duration(:w).should == Duration.from_s('w')
357
+ end
358
+
359
+ it "acts like .[] if the argument is a Numeric" do
360
+ Duration(3.5).should be_equal Duration[Rational(7,2)]
361
+ end
362
+
363
+ it "returns the argument if it's already a Duration" do
364
+ Duration(Duration[1]).should be_equal Duration[1]
365
+ end
366
+
367
+ it "raises an error for types it doesn't understand" do
368
+ lambda{ Duration({:not => :compatible}) }.should raise_error
369
+ end
370
+ end
371
+
372
+ end