jmtk 0.0.3.3-java

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 (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
+