mtk 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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