jmtk 0.0.3.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -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/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. metadata +226 -0
@@ -0,0 +1,234 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Events::Event do
4
+
5
+ EVENT = MTK::Events::Event
6
+
7
+ let(:type) { :type }
8
+ let(:duration) { 2.5 }
9
+ let(:options) { {:number => 1, :value => 0.5, :duration => duration, :channel => 0} }
10
+ let(:event) { EVENT.new type, options }
11
+ let(:hash) { options.merge({:type => type}) }
12
+
13
+
14
+ describe "#type" do
15
+ it "is the first argument passed to AbstractEvent.new" do
16
+ event.type.should == type
17
+ end
18
+
19
+ it "is a read-only attribute" do
20
+ lambda{ event.type = :anything }.should raise_error
21
+ end
22
+ end
23
+
24
+ describe "#value" do
25
+ it "is the value of the :value key in the options hash passed to AbstractEvent.new" do
26
+ event.value.should == options[:value]
27
+ end
28
+
29
+ it "defaults to nil" do
30
+ EVENT.new(type).value.should be_nil
31
+ end
32
+ end
33
+
34
+ describe "#value=" do
35
+ it "sets #value" do
36
+ event.value = 42
37
+ event.value.should == 42
38
+ end
39
+ end
40
+
41
+ describe "#duration" do
42
+ it "is the value of the :duration key in the options hash passed to AbstractEvent.new" do
43
+ event.duration.should == options[:duration]
44
+ end
45
+
46
+ it "defaults to 0" do
47
+ EVENT.new(type).duration.should == 0
48
+ end
49
+ end
50
+
51
+ describe "#duration=" do
52
+ it "sets #duration" do
53
+ event.duration = 42
54
+ event.duration.should == 42
55
+ end
56
+ end
57
+
58
+ describe "#number" do
59
+ it "is the value of the :number key in the options hash passed to AbstractEvent.new" do
60
+ event.number.should == options[:number]
61
+ end
62
+
63
+ it "defaults to nil" do
64
+ EVENT.new(type).number.should be_nil
65
+ end
66
+ end
67
+
68
+ describe "#number=" do
69
+ it "sets #number" do
70
+ event.number = 42
71
+ event.number.should == 42
72
+ end
73
+ end
74
+
75
+ describe "#channel" do
76
+ it "is the value of the :channel key in the options hash passed to AbstractEvent.new" do
77
+ event.channel.should == options[:channel]
78
+ end
79
+
80
+ it "defaults to nil" do
81
+ EVENT.new(type).channel.should be_nil
82
+ end
83
+ end
84
+
85
+ describe "#channel=" do
86
+ it "sets #channel" do
87
+ event.channel = 42
88
+ event.channel.should == 42
89
+ end
90
+ end
91
+
92
+ describe "#midi_value" do
93
+ it "is the @value * 127, rounded to the nearest integer" do
94
+ event.midi_value.should == (options[:value]*127).round
95
+ end
96
+
97
+ it "defaults to 0 when the @value is nil" do
98
+ EVENT.new(type).midi_value.should == 0
99
+ end
100
+
101
+ it "enforces a minimum of 0" do
102
+ event.value = -2
103
+ event.midi_value.should == 0
104
+ end
105
+
106
+ it "enforces a maximum of 127" do
107
+ event.value = 2
108
+ event.midi_value.should == 127
109
+ end
110
+ end
111
+
112
+ describe "#midi_value=" do
113
+ it "sets #value to the argument/127.0" do
114
+ event.midi_value = 100
115
+ event.value.should == 100/127.0
116
+ end
117
+ end
118
+
119
+ describe "#length" do
120
+ it "is the absolute value of duration" do
121
+ event.duration = -duration
122
+ event.length.should == duration
123
+ end
124
+
125
+ it "is 0 if the #duration is nil" do
126
+ event.duration = nil
127
+ event.length.should == 0
128
+ end
129
+ end
130
+
131
+ describe "#rest?" do
132
+ it "is true when the duration is negative" do
133
+ event.duration = -duration
134
+ event.rest?.should be_true
135
+ end
136
+
137
+ it "is false when the duration is positive" do
138
+ event.rest?.should be_false
139
+ end
140
+ end
141
+
142
+ describe "#instantaneous?" do
143
+ it "is true when the duration is 0" do
144
+ event.duration = 0
145
+ event.instantaneous?.should be_true
146
+ end
147
+
148
+ it "is true when the duration is nil" do
149
+ event.duration = nil
150
+ event.instantaneous?.should be_true
151
+ end
152
+
153
+ it "is false when the duration is positive" do
154
+ event.instantaneous?.should be_false
155
+ end
156
+ end
157
+
158
+ describe "#duration_in_pulses" do
159
+ it "multiplies the #length times the argument and rounds to the nearest integer" do
160
+ event.duration_in_pulses(111).should == (event.length * 111).round
161
+ end
162
+
163
+ it "is 0 when the #duration is nil" do
164
+ event.duration = nil
165
+ event.duration_in_pulses(111).should == 0
166
+ end
167
+ end
168
+
169
+ describe "from_h" do
170
+ it "constructs an Event using a hash" do
171
+ EVENT.from_h(hash).should == event
172
+ end
173
+ end
174
+
175
+ describe "#to_h" do
176
+ it "is a hash containing all the attributes of the Event" do
177
+ event.to_h.should == hash
178
+ end
179
+ end
180
+
181
+ describe "#duration_in_pulses" do
182
+ it "converts beats to pulses, given pulses_per_beat" do
183
+ EVENT.new(type, :duration => 1).duration_in_pulses(60).should == 60
184
+ end
185
+
186
+ it "rounds to the nearest pulse" do
187
+ EVENT.new(type, :duration => 1.5).duration_in_pulses(59).should == 89
188
+ end
189
+
190
+ it "is always positive (uses absolute value of the duration used to construct the Event)" do
191
+ EVENT.new(type, :duration => -1).duration_in_pulses(60).should == 60
192
+ end
193
+ end
194
+
195
+ describe "#==" do
196
+ it "is true when the type, number, value, duration and channel are equal" do
197
+ event.should == EVENT.new(type, options)
198
+ end
199
+ it "is false when the types are not equal" do
200
+ event.should_not == EVENT.new(:another_type, options)
201
+ end
202
+ it "is false when the numbers are not equal" do
203
+ event.should_not == EVENT.new(type, options.merge({:number => event.number + 1}))
204
+ end
205
+ it "is false when the values are not equal" do
206
+ event.should_not == EVENT.new(type, options.merge({:value => event.value * 0.5}))
207
+ end
208
+ it "is false when the durations are not equal" do
209
+ event.should_not == EVENT.new(type, options.merge({:duration => event.duration * 2}))
210
+ end
211
+ it "is false when the channels are not equal" do
212
+ event.should_not == EVENT.new(type, options.merge({:channel => event.channel + 1}))
213
+ end
214
+ end
215
+
216
+ describe "#to_s" do
217
+ it "has the value and duration to 2-decimal places" do
218
+ EVENT.new(type, :value => 0.454545, :duration => 0.789789).to_s.should == "Event(type, 0.45, 0.79)"
219
+ end
220
+ it "includes the #number when not nil" do
221
+ EVENT.new(type, :value => 0.454545, :duration => 0.789789, :number => 1).to_s.should == "Event(type[1], 0.45, 0.79)"
222
+ end
223
+ end
224
+
225
+ describe "#inspect" do
226
+ it "has the string values of value and duration" do
227
+ EVENT.new(:type, :value => 0.454545, :duration => 0.789789).inspect.should == "Event(type, 0.454545, 0.789789)"
228
+ end
229
+ it "includes the #number when not nil" do
230
+ EVENT.new(type, :value => 0.454545, :duration => 0.789789, :number => 1).inspect.should == "Event(type[1], 0.454545, 0.789789)"
231
+ end
232
+ end
233
+
234
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Events::Note do
4
+
5
+ NOTE = Events::Note
6
+
7
+ let(:pitch) { C4 }
8
+ let(:intensity) { mf }
9
+ let(:duration) { Duration[2.5] }
10
+ let(:note) { NOTE.new(pitch, duration, intensity) }
11
+
12
+ describe "#pitch" do
13
+ it "is the pitch used to create the Note" do
14
+ note.pitch.should == pitch
15
+ end
16
+ end
17
+
18
+ describe "#pitch=" do
19
+ it "sets the pitch" do
20
+ note.pitch = D4
21
+ note.pitch.should == D4
22
+ end
23
+ end
24
+
25
+ describe "#intensity" do
26
+ it "is the intensity used to create the Event" do
27
+ note.intensity.should == intensity
28
+ end
29
+ end
30
+
31
+ describe "#intensity=" do
32
+ it "sets the intensity" do
33
+ note.intensity = fff
34
+ note.intensity.should == fff
35
+ end
36
+ end
37
+
38
+ describe "#velocity" do
39
+ it "converts intensities in the range 0.0-1.0 to a MIDI velocity in the range 0-127" do
40
+ NOTE.new(pitch, 0, 0).velocity.should == 0
41
+ NOTE.new(pitch, 0, 1).velocity.should == 127
42
+ end
43
+ it "rounds to the nearest MIDI velocity" do
44
+ NOTE.new(pitch, 0, 0.5).velocity.should == 64 # not be truncated to 63!
45
+ end
46
+ end
47
+
48
+ describe "#velocity=" do
49
+ it "sets the velocity" do
50
+ note.velocity = 100
51
+ note.velocity.should == 100
52
+ end
53
+ end
54
+
55
+ describe ".from_h" do
56
+ it "constructs a Note using a hash" do
57
+ NOTE.from_h({ :pitch => pitch, :intensity => intensity, :duration => duration }).should == note
58
+ end
59
+ end
60
+
61
+ describe '.from_midi' do
62
+ it "constructs a Note using a MIDI pitch and velocity" do
63
+ NOTE.from_midi(C4.to_i, mf.value*127, 2.5).should == note
64
+ end
65
+ end
66
+
67
+ describe "#to_h" do
68
+ it "is a hash containing all the attributes of the Note" do
69
+ hash = note.to_h
70
+ # hash includes some extra "baggage" for compatibility with AbstractEvent,
71
+ # so we'll just check the fields we care about:
72
+ hash[:pitch].should == pitch
73
+ hash[:intensity].should == intensity
74
+ hash[:duration].should == duration
75
+ end
76
+ end
77
+
78
+ describe '#transpose' do
79
+ it 'adds the given interval to the @pitch' do
80
+ (note.transpose 2).should == NOTE.new(D4, duration, intensity)
81
+ end
82
+ end
83
+
84
+ describe "#invert" do
85
+ context 'higher center pitch' do
86
+ it 'inverts the pitch around the given center pitch' do
87
+ note.invert(Pitch 66).should == NOTE.new(Pitch(72), duration, intensity)
88
+ end
89
+ end
90
+
91
+ context 'lower center pitch' do
92
+ it 'inverts the pitch around the given center pitch' do
93
+ note.invert(Pitch 54).should == NOTE.new(Pitch(48), duration, intensity)
94
+ end
95
+ end
96
+
97
+ it "returns the an equal note when given it's pitch as an argument" do
98
+ note.invert(note.pitch).should == note
99
+ end
100
+ end
101
+
102
+ describe "#==" do
103
+ it "is true when the pitches, intensities, and durations are equal" do
104
+ note.should == NOTE.new(pitch, duration, intensity)
105
+ end
106
+
107
+ it "is false when the pitches are not equal" do
108
+ note.should_not == NOTE.new(pitch + 1, duration, intensity)
109
+ end
110
+
111
+ it "is false when the intensities are not equal" do
112
+ note.should_not == NOTE.new(pitch, duration, intensity * 0.5)
113
+ end
114
+
115
+ it "is false when the durations are not equal" do
116
+ note.should_not == NOTE.new(pitch, duration * 2, intensity)
117
+ end
118
+ end
119
+
120
+ describe "#to_s" do
121
+ it "includes the #pitch, #intensity to 2 decimal places, and #duration to 2 decimal places" do
122
+ NOTE.new(C4, Duration(1/3.0), Intensity(1/3.0)).to_s.should == "Note(C4, 0.33 beat, 33%)"
123
+ end
124
+ end
125
+
126
+ describe "#inspect" do
127
+ it 'is "#<MTK::Events::Note:{object_id} @pitch={pitch.inspect}, @duration={duration.inspect}, @intensity={intensity.inspect}>"' do
128
+ duration = MTK.Duration(1/8.0)
129
+ intensity = MTK.Intensity(1/8.0)
130
+ note = NOTE.new(C4, duration, intensity)
131
+ note.inspect.should == "#<MTK::Events::Note:#{note.object_id} @pitch=#{C4.inspect}, @duration=#{duration.inspect}, @intensity=#{intensity.inspect}>"
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+
138
+ describe MTK do
139
+
140
+ describe '#Note' do
141
+
142
+ it "acts like new for multiple arguments" do
143
+ Note(C4,q,mf).should == NOTE.new(C4,q,mf)
144
+ end
145
+
146
+ it "acts like new for an Array of arguments by unpacking (splatting) them" do
147
+ Note([C4,q,mf]).should == NOTE.new(C4,q,mf)
148
+ end
149
+
150
+ it "returns the argument if it's already a Note" do
151
+ note = NOTE.new(C4,q,mf)
152
+ Note(note).should be_equal(note)
153
+ end
154
+
155
+ it "raises an error for types it doesn't understand" do
156
+ lambda{ Note({:not => :compatible}) }.should raise_error
157
+ end
158
+
159
+ it "handles out of order arguments for recognized types (Pitch, Duration, Intensity)" do
160
+ Note(q,mf,C4).should == NOTE.new(C4,q,mf)
161
+ end
162
+
163
+ it "fills in a missing duration type from an number" do
164
+ Note(C4,mf,5.25).should == NOTE.new(C4,MTK.Duration(5.25),mf)
165
+ end
166
+
167
+ it '' do
168
+ Note(MTK::Lang::Pitches::C4, MTK::Lang::Intensities::o, 5.25).should == Note(C4, 5.25, 0.75)
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+
@@ -0,0 +1,220 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Events::Parameter do
4
+
5
+ PARAMETER = MTK::Events::Parameter
6
+
7
+
8
+ describe ".from_midi" do
9
+
10
+ context "poly pressure events" do
11
+
12
+ context "first arg is the status byte" do
13
+ it "parses :pressure #type on channel 0" do
14
+ PARAMETER.from_midi(0xA0, 0, 0).type.should == :pressure
15
+ end
16
+ it "parses :pressure #type on other channels" do
17
+ PARAMETER.from_midi(0xA0 | 5, 0, 0).type.should == :pressure
18
+ end
19
+ it "parses the #channel" do
20
+ PARAMETER.from_midi(0xA0 | 5, 0, 0).channel.should == 5
21
+ end
22
+ end
23
+
24
+ context "first arg is an array of [type,channel]" do
25
+ it "converts :poly_pressure to :pressure #type" do
26
+ PARAMETER.from_midi([:poly_pressure, 0], 0, 0).type.should == :pressure
27
+ end
28
+ it "extracts the #channel" do
29
+ PARAMETER.from_midi([:poly_pressure, 5], 0, 0).channel.should == 5
30
+ end
31
+ end
32
+
33
+ it "sets #number (pitch) to the data1 byte" do
34
+ PARAMETER.from_midi(0xA0, 100, 50).number.should == 100
35
+ end
36
+
37
+ it "set #value to the data2 byte / 127.0" do
38
+ PARAMETER.from_midi(0xA0, 100, 50).value.should == 50/127.0
39
+ end
40
+ end
41
+
42
+
43
+ context "control change events" do
44
+
45
+ context "first arg is the status byte" do
46
+ it "parses :control #type on channel 0" do
47
+ PARAMETER.from_midi(0xB0, 0, 0).type.should == :control
48
+ end
49
+ it "parses :control #type on other channels" do
50
+ PARAMETER.from_midi(0xB0 | 5, 0, 0).type.should == :control
51
+ end
52
+ it "parses the #channel" do
53
+ PARAMETER.from_midi(0xB0 | 5, 0, 0).channel.should == 5
54
+ end
55
+ end
56
+
57
+ context "first arg is an array of [type,channel]" do
58
+ it "converts :control_change :control #type" do
59
+ PARAMETER.from_midi([:control_change, 0], 0, 0).type.should == :control
60
+ end
61
+ it "extracts the #channel" do
62
+ PARAMETER.from_midi([:control_change, 5], 0, 0).channel.should == 5
63
+ end
64
+ end
65
+
66
+ it "sets #number to the data1 byte" do
67
+ PARAMETER.from_midi(0xB0, 100, 50).number.should == 100
68
+ end
69
+
70
+ it "set #value to the data2 byte / 127.0" do
71
+ PARAMETER.from_midi(0xB0, 100, 50).value.should == 50/127.0
72
+ end
73
+ end
74
+
75
+
76
+ context "program change events" do
77
+
78
+ context "first arg is the status byte" do
79
+ it "parses :program #type on channel 0" do
80
+ PARAMETER.from_midi(0xC0, 0, 0).type.should == :program
81
+ end
82
+ it "parses :program #type on other channels" do
83
+ PARAMETER.from_midi(0xC0 | 5, 0, 0).type.should == :program
84
+ end
85
+ it "parses the #channel" do
86
+ PARAMETER.from_midi(0xC0 | 5, 0, 0).channel.should == 5
87
+ end
88
+ end
89
+
90
+ context "first arg is an array of [type,channel]" do
91
+ it "converts :program_change :program #type" do
92
+ PARAMETER.from_midi([:program_change, 0], 0, 0).type.should == :program
93
+ end
94
+ it "extracts the #channel" do
95
+ PARAMETER.from_midi([:program_change, 5], 0, 0).channel.should == 5
96
+ end
97
+ end
98
+
99
+ it "sets #number to the data1 byte" do
100
+ PARAMETER.from_midi(0xC0, 100, 0).number.should == 100
101
+ end
102
+
103
+ it "does not set #value" do
104
+ PARAMETER.from_midi(0xC0, 100, 0).value.should be_nil
105
+ end
106
+ end
107
+
108
+
109
+ context "channel pressure events" do
110
+
111
+ context "first arg is the status byte" do
112
+ it "parses :pressure #type on channel 0" do
113
+ PARAMETER.from_midi(0xD0, 0, 0).type.should == :pressure
114
+ end
115
+ it "parses :pressure #type on other channels" do
116
+ PARAMETER.from_midi(0xD0 | 5, 0, 0).type.should == :pressure
117
+ end
118
+ it "parses the #channel" do
119
+ PARAMETER.from_midi(0xD0 | 5, 0, 0).channel.should == 5
120
+ end
121
+ end
122
+
123
+ context "first arg is an array of [type,channel]" do
124
+ it "converts :channel_pressure to :pressure #type" do
125
+ PARAMETER.from_midi([:channel_pressure, 0], 0, 0).type.should == :pressure
126
+ end
127
+ it "extracts the #channel" do
128
+ PARAMETER.from_midi([:channel_pressure, 5], 0, 0).channel.should == 5
129
+ end
130
+ end
131
+
132
+ it "does not set #number" do
133
+ PARAMETER.from_midi(0xD0, 50, 0).number.should be_nil
134
+ end
135
+
136
+ it "set #value to the data1 byte / 127.0" do
137
+ PARAMETER.from_midi(0xD0, 50, 0).value.should == 50/127.0
138
+ end
139
+ end
140
+
141
+
142
+ context "pitch bend events" do
143
+
144
+ context "first arg is the status byte" do
145
+ it "parses :bend #type on channel 0" do
146
+ PARAMETER.from_midi(0xE0, 0, 0).type.should == :bend
147
+ end
148
+ it "parses :bend #type on other channels" do
149
+ PARAMETER.from_midi(0xE0 | 5, 0, 0).type.should == :bend
150
+ end
151
+ it "parses the #channel" do
152
+ PARAMETER.from_midi(0xE0 | 5, 0, 0).channel.should == 5
153
+ end
154
+ end
155
+
156
+ context "first arg is an array of [type,channel]" do
157
+ it "converts :pitch_bend :bend #type" do
158
+ PARAMETER.from_midi([:pitch_bend, 0], 0, 0).type.should == :bend
159
+ end
160
+ it "extracts the #channel" do
161
+ PARAMETER.from_midi([:pitch_bend, 5], 0, 0).channel.should == 5
162
+ end
163
+ end
164
+
165
+ it "does not set #number" do
166
+ PARAMETER.from_midi(0xE0, 50, 0).number.should be_nil
167
+ end
168
+
169
+ it "sets #value by combining the data1 (lsb) and data2 (msb) bytes into a 14-bit int and then mapping to the range -1.0..1.0" do
170
+ PARAMETER.from_midi(0xE0, 0, 0).value.should == -1.0
171
+ PARAMETER.from_midi(0xE0, 0, 64).value.should == 0
172
+ PARAMETER.from_midi(0xE0, 127, 127).value.should == 1.0
173
+ end
174
+ end
175
+
176
+
177
+ context "unknown events" do
178
+ it "sets #type to :unknown" do
179
+ PARAMETER.from_midi(0xF0, 50, 100).type.should == :unknown
180
+ end
181
+
182
+ it "sets #number to the data1 byte" do
183
+ PARAMETER.from_midi(0xF0, 50, 100).number.should == 50
184
+ end
185
+
186
+ it "sets #value to the data2 byte / 127.0" do
187
+ PARAMETER.from_midi(0xF0, 50, 100).value.should == 100/127.0
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+
194
+ describe "#midi_value" do
195
+ it "special cases :bend type events to map the range -1.0..1.0 back to a 14-bit int" do
196
+ PARAMETER.from_midi([:pitch_bend,0], 0, 0).midi_value.should == 0
197
+ PARAMETER.from_midi([:pitch_bend,0], 0, 64).midi_value.should == 8192
198
+ PARAMETER.from_midi([:pitch_bend,0], 127, 127).midi_value.should == 16383
199
+ end
200
+
201
+ it "maps back to the original midi value for other cases" do
202
+ PARAMETER.from_midi([:control_change,0], 0, 50).midi_value.should == 50
203
+ end
204
+ end
205
+
206
+
207
+ describe "#to_s" do
208
+ it "includes the #type, #number, and #value to 2 decimal places" do
209
+ PARAMETER.from_midi([:control_change,0], 50, 100).to_s.should == "Parameter(control[50], 0.79)"
210
+ end
211
+ end
212
+
213
+ describe "#inspect" do
214
+ it "includes the #type, #number, and #value.to_s" do
215
+ PARAMETER.from_midi([:control_change,0], 50, 100).inspect.should == "Parameter(control[50], #{100/127.0})"
216
+ end
217
+ end
218
+
219
+ end
220
+