jmtk 0.0.3.3-java → 0.4-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +15 -0
  2. data/DEVELOPMENT_NOTES.md +20 -0
  3. data/INTRO.md +63 -31
  4. data/README.md +9 -3
  5. data/Rakefile +42 -42
  6. data/bin/jmtk +75 -32
  7. data/bin/mtk +75 -32
  8. data/examples/drum_pattern.rb +2 -2
  9. data/examples/dynamic_pattern.rb +1 -1
  10. data/examples/helpers/output_selector.rb +71 -0
  11. data/examples/notation.rb +5 -1
  12. data/examples/tone_row_melody.rb +1 -1
  13. data/lib/mtk.rb +1 -0
  14. data/lib/mtk/core/duration.rb +18 -3
  15. data/lib/mtk/core/intensity.rb +5 -3
  16. data/lib/mtk/core/interval.rb +21 -14
  17. data/lib/mtk/core/pitch.rb +2 -0
  18. data/lib/mtk/core/pitch_class.rb +6 -3
  19. data/lib/mtk/events/event.rb +2 -1
  20. data/lib/mtk/events/note.rb +1 -1
  21. data/lib/mtk/events/parameter.rb +1 -0
  22. data/lib/mtk/events/rest.rb +85 -0
  23. data/lib/mtk/events/timeline.rb +6 -2
  24. data/lib/mtk/io/jsound_input.rb +9 -3
  25. data/lib/mtk/io/midi_file.rb +38 -2
  26. data/lib/mtk/io/midi_input.rb +1 -1
  27. data/lib/mtk/io/midi_output.rb +95 -4
  28. data/lib/mtk/io/unimidi_input.rb +7 -3
  29. data/lib/mtk/lang/durations.rb +31 -26
  30. data/lib/mtk/lang/intensities.rb +29 -30
  31. data/lib/mtk/lang/intervals.rb +108 -41
  32. data/lib/mtk/lang/mtk_grammar.citrus +14 -4
  33. data/lib/mtk/lang/parser.rb +10 -5
  34. data/lib/mtk/lang/pitch_classes.rb +45 -17
  35. data/lib/mtk/lang/pitches.rb +169 -32
  36. data/lib/mtk/lang/tutorial.rb +279 -0
  37. data/lib/mtk/lang/tutorial_lesson.rb +87 -0
  38. data/lib/mtk/sequencers/event_builder.rb +29 -8
  39. data/spec/mtk/core/duration_spec.rb +14 -1
  40. data/spec/mtk/core/intensity_spec.rb +1 -1
  41. data/spec/mtk/events/event_spec.rb +10 -16
  42. data/spec/mtk/events/note_spec.rb +3 -3
  43. data/spec/mtk/events/rest_spec.rb +184 -0
  44. data/spec/mtk/events/timeline_spec.rb +5 -1
  45. data/spec/mtk/io/midi_file_spec.rb +13 -2
  46. data/spec/mtk/io/midi_output_spec.rb +42 -9
  47. data/spec/mtk/lang/durations_spec.rb +5 -5
  48. data/spec/mtk/lang/intensities_spec.rb +5 -5
  49. data/spec/mtk/lang/intervals_spec.rb +139 -13
  50. data/spec/mtk/lang/parser_spec.rb +65 -25
  51. data/spec/mtk/lang/pitch_classes_spec.rb +0 -11
  52. data/spec/mtk/lang/pitches_spec.rb +0 -15
  53. data/spec/mtk/patterns/chain_spec.rb +7 -7
  54. data/spec/mtk/patterns/for_each_spec.rb +2 -2
  55. data/spec/mtk/sequencers/event_builder_spec.rb +49 -17
  56. metadata +12 -22
@@ -7,7 +7,7 @@ describe MTK::Core::Intensity do
7
7
 
8
8
  describe 'NAMES' do
9
9
  it "is the list of base intensity names available" do
10
- Intensity::NAMES.should =~ %w( ppp pp p mp mf o ff fff )
10
+ Intensity::NAMES.should =~ %w( ppp pp p mp mf f ff fff )
11
11
  end
12
12
 
13
13
  it "is immutable" do
@@ -12,7 +12,7 @@ describe MTK::Events::Event do
12
12
 
13
13
 
14
14
  describe "#type" do
15
- it "is the first argument passed to AbstractEvent.new" do
15
+ it "is the first argument passed to Event.new" do
16
16
  event.type.should == type
17
17
  end
18
18
 
@@ -22,7 +22,7 @@ describe MTK::Events::Event do
22
22
  end
23
23
 
24
24
  describe "#value" do
25
- it "is the value of the :value key in the options hash passed to AbstractEvent.new" do
25
+ it "is the value of the :value key in the options hash passed to Event.new" do
26
26
  event.value.should == options[:value]
27
27
  end
28
28
 
@@ -39,7 +39,7 @@ describe MTK::Events::Event do
39
39
  end
40
40
 
41
41
  describe "#duration" do
42
- it "is the value of the :duration key in the options hash passed to AbstractEvent.new" do
42
+ it "is the value of the :duration key in the options hash passed to Event.new" do
43
43
  event.duration.should == options[:duration]
44
44
  end
45
45
 
@@ -56,7 +56,7 @@ describe MTK::Events::Event do
56
56
  end
57
57
 
58
58
  describe "#number" do
59
- it "is the value of the :number key in the options hash passed to AbstractEvent.new" do
59
+ it "is the value of the :number key in the options hash passed to Event.new" do
60
60
  event.number.should == options[:number]
61
61
  end
62
62
 
@@ -73,7 +73,7 @@ describe MTK::Events::Event do
73
73
  end
74
74
 
75
75
  describe "#channel" do
76
- it "is the value of the :channel key in the options hash passed to AbstractEvent.new" do
76
+ it "is the value of the :channel key in the options hash passed to Event.new" do
77
77
  event.channel.should == options[:channel]
78
78
  end
79
79
 
@@ -155,17 +155,6 @@ describe MTK::Events::Event do
155
155
  end
156
156
  end
157
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
158
  describe "from_h" do
170
159
  it "constructs an Event using a hash" do
171
160
  EVENT.from_h(hash).should == event
@@ -187,6 +176,11 @@ describe MTK::Events::Event do
187
176
  EVENT.new(type, :duration => 1.5).duration_in_pulses(59).should == 89
188
177
  end
189
178
 
179
+ it "is 0 when the #duration is nil" do
180
+ event.duration = nil
181
+ event.duration_in_pulses(111).should == 0
182
+ end
183
+
190
184
  it "is always positive (uses absolute value of the duration used to construct the Event)" do
191
185
  EVENT.new(type, :duration => -1).duration_in_pulses(60).should == 60
192
186
  end
@@ -160,12 +160,12 @@ describe MTK do
160
160
  Note(q,mf,C4).should == NOTE.new(C4,q,mf)
161
161
  end
162
162
 
163
- it "fills in a missing duration type from an number" do
163
+ it "fills in a missing duration argument from an number" do
164
164
  Note(C4,mf,5.25).should == NOTE.new(C4,MTK.Duration(5.25),mf)
165
165
  end
166
166
 
167
- it '' do
168
- Note(MTK::Lang::Pitches::C4, MTK::Lang::Intensities::o, 5.25).should == Note(C4, 5.25, 0.75)
167
+ it 'fills in a missing intensity and duration arguments from numbers' do
168
+ Note(MTK::Lang::Pitches::C4, MTK::Lang::Intensities::f, 5.25).should == Note(C4, 5.25, 0.75)
169
169
  end
170
170
 
171
171
  end
@@ -0,0 +1,184 @@
1
+ require 'spec_helper'
2
+
3
+ describe MTK::Events::Rest do
4
+
5
+ REST = MTK::Events::Rest
6
+
7
+ let(:duration) { -7.5 }
8
+ let(:channel) { 3 }
9
+ let(:rest) { REST.new duration, channel }
10
+ let(:hash) { {type: :rest, duration: duration, channel: channel} }
11
+
12
+
13
+ describe ".new" do
14
+ it "requires a duration as the first argument" do
15
+ lambda{ REST.new() }.should raise_error
16
+ end
17
+
18
+ it "coerces the duration to an MTK::Core::Duration" do
19
+ rest.duration.should be_a MTK::Core::Duration
20
+ end
21
+
22
+ it "forces the duration value negative to be a rest, if needed" do
23
+ REST.new(5).duration.should == -5
24
+ end
25
+ end
26
+
27
+ describe "#type" do
28
+ it "is :rest" do
29
+ rest.type.should == :rest
30
+ end
31
+
32
+ it "is a read-only attribute" do
33
+ lambda{ rest.type = :anything }.should raise_error
34
+ end
35
+ end
36
+
37
+ describe "#duration" do
38
+ it "is the value of the first argument to Rest.new, if the value was negative (indicating a rest)" do
39
+ rest.duration.should == duration
40
+ end
41
+ end
42
+
43
+ describe "#duration=" do
44
+ it "sets #duration" do
45
+ rest.duration = -42
46
+ rest.duration.should == -42
47
+ end
48
+
49
+ it "forces coerces argument to a MTK::Core::Duration" do
50
+ rest.duration = 42
51
+ rest.duration.should be_a MTK::Core::Duration
52
+ end
53
+
54
+ it "forces the duration value negative to be a rest, if needed" do
55
+ rest.duration = 42
56
+ rest.duration.should == -42
57
+ end
58
+ end
59
+
60
+ describe "#channel" do
61
+ it "is the value of the :channel key in the options hash passed to Rest.new" do
62
+ rest.channel.should == channel
63
+ end
64
+
65
+ it "defaults to nil" do
66
+ REST.new(duration).channel.should be_nil
67
+ end
68
+ end
69
+
70
+ describe "#channel=" do
71
+ it "sets #channel" do
72
+ rest.channel = 12
73
+ rest.channel.should == 12
74
+ end
75
+ end
76
+
77
+ describe "#midi_value" do
78
+ it "is nil" do
79
+ rest.midi_value.should be_nil
80
+ end
81
+ end
82
+
83
+ describe "#length" do
84
+ it "is the absolute value of duration" do
85
+ rest.duration = -5
86
+ rest.length.should == 5
87
+ end
88
+ end
89
+
90
+ describe "#rest?" do
91
+ it "is true when the duration is negative" do
92
+ rest.rest?.should be_true
93
+ end
94
+
95
+ it "is true event with a positive duration, because the duration was forced negative" do
96
+ rest.duration = 5
97
+ rest.rest?.should be_true
98
+ end
99
+ end
100
+
101
+ describe "from_h" do
102
+ it "constructs an Event using a hash" do
103
+ REST.from_h(hash).should == rest
104
+ end
105
+ end
106
+
107
+ describe "#to_h" do
108
+ it "is a hash containing all the attributes of the Rest" do
109
+ rest.to_h.should == hash
110
+ end
111
+ end
112
+
113
+
114
+ describe "#==" do
115
+ it "is true when the duration and channel are equal" do
116
+ rest.should == REST.new(duration, channel)
117
+ end
118
+
119
+ it "is false when the durations are not equal" do
120
+ rest.should_not == REST.new(duration+1, channel)
121
+ end
122
+
123
+ it "is false when the channels are not equal" do
124
+ rest.should_not == REST.new(duration, channel+1)
125
+ end
126
+ end
127
+
128
+ describe "#to_s" do
129
+ it "includes #duration to 2 decimal places" do
130
+ REST.new(Duration(-1/3.0)).to_s.should == "Rest(-0.33 beat)"
131
+ end
132
+ end
133
+
134
+ describe "#inspect" do
135
+ it 'is "#<MTK::Events::Rest:{object_id} @duration={duration.inspect}, @channel={channel}>"' do
136
+ rest.inspect.should == "#<MTK::Events::Rest:#{rest.object_id} @duration=#{rest.duration.inspect}, @channel=#{channel}>"
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+
143
+ describe MTK do
144
+
145
+ describe '#Rest' do
146
+
147
+ it "acts like new for multiple arguments" do
148
+ MTK::Rest(4,2).should == REST.new(4,2)
149
+ end
150
+
151
+ it "acts like new for an Array of arguments by unpacking (splatting) them" do
152
+ MTK::Rest([4,2]).should == REST.new(4,2)
153
+ end
154
+
155
+ it "returns the argument if it's already a Rest" do
156
+ rest = REST.new(4,2)
157
+ MTK::Rest(rest).should be_equal(rest)
158
+ end
159
+
160
+ it "makes a Rest of the same duration and channel from other Event types" do
161
+ event = MTK::Events::Event.new(:event_type, duration:5, channel:3)
162
+ MTK::Rest(event).should == REST.new(5,3)
163
+ end
164
+
165
+ it "handles a single Numeric argument" do
166
+ MTK::Rest(4).should == REST.new(4)
167
+ end
168
+
169
+ it "handles a single Duration argument" do
170
+ MTK.Rest(MTK.Duration(4)).should == REST.new(4)
171
+ end
172
+
173
+ it "raises an error for types it doesn't understand" do
174
+ lambda{ MTK::Rest({:not => :compatible}) }.should raise_error
175
+ end
176
+
177
+ it "handles out of order arguments for recognized Duration types" do
178
+ MTK::Rest(2,q).should == REST.new(q,2)
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe MTK::Events::Timeline do
4
4
 
5
5
  let(:note1) { Note(C4, p, 1) }
6
- let(:note2) { Note(G4, o, 2) }
6
+ let(:note2) { Note(G4, f, 2) }
7
7
  let(:timeline_raw_data) { { 0.0 => note1, 1.0 => [note1, note2] } }
8
8
  let(:timeline_hash) { { 0.0 => [note1], 1.0 => [note1, note2] } }
9
9
  let(:timeline) { MTK::Events::Timeline.from_h(timeline_raw_data) }
@@ -425,6 +425,10 @@ describe MTK::Events::Timeline do
425
425
  (line =~ /=>/).should == arrow_position
426
426
  end
427
427
  end
428
+
429
+ it "returns empty string for empty timelines" do
430
+ MTK::Events::Timeline.new.to_s.should == ''
431
+ end
428
432
  end
429
433
  end
430
434
 
@@ -42,7 +42,7 @@ describe MTK::IO::MIDIFile do
42
42
  end
43
43
 
44
44
  describe "#write_timeline" do
45
- it 'writes monophonic Notes in a Timeline to a MIDI file' do
45
+ it 'writes monophonic Notes in a Timeline to a MIDI format 0 file' do
46
46
  MIDIFile(tempfile).write_timeline(
47
47
  MTK::Events::Timeline.from_h({
48
48
  0 => Note(C4, q, 0.7),
@@ -56,6 +56,7 @@ describe MTK::IO::MIDIFile do
56
56
  seq = MIDI::Sequence.new
57
57
  seq.read(file)
58
58
  seq.tracks.size.should == 1
59
+ seq.format.should == 0
59
60
 
60
61
  track = seq.tracks[0]
61
62
  note_ons, note_offs = note_ons_and_offs(track)
@@ -82,6 +83,15 @@ describe MTK::IO::MIDIFile do
82
83
  end
83
84
  end
84
85
 
86
+ it 'writes a timeline that can be read in as an identical Timeline' do
87
+ timeline = MTK::Events::Timeline.from_h({
88
+ 0 => Note(C4, q, 1)
89
+ })
90
+ MIDIFile(tempfile).write_timeline(timeline)
91
+ read_timeline = MIDIFile(tempfile).to_timelines.first
92
+ read_timeline.should == timeline
93
+ end
94
+
85
95
  it 'writes polyphonic (simultaneous) Notes in a Timeline to a MIDI file' do
86
96
  MIDIFile(tempfile).write_timeline(
87
97
  MTK::Events::Timeline.from_h({
@@ -169,7 +179,7 @@ describe MTK::IO::MIDIFile do
169
179
  end
170
180
 
171
181
  describe "#write_timelines" do
172
- it "writes a multitrack MIDI file" do
182
+ it "writes a multitrack format 1 MIDI file" do
173
183
  MIDIFile(tempfile).write_timelines([
174
184
  MTK::Events::Timeline.from_h({
175
185
  0 => Note(C4, q, 0.7),
@@ -186,6 +196,7 @@ describe MTK::IO::MIDIFile do
186
196
  seq = MIDI::Sequence.new
187
197
  seq.read(file)
188
198
  seq.tracks.size.should == 2
199
+ seq.format.should == 1
189
200
 
190
201
  track = seq.tracks[0]
191
202
  note_ons, note_offs = note_ons_and_offs(track)
@@ -10,14 +10,15 @@ describe MTK::IO::MIDIOutput do
10
10
  let(:subject) { MockOuput.new(mock_device) }
11
11
 
12
12
  let(:mock_device) do
13
- mock_device = mock(:device)
13
+ mock_device = double(:device)
14
14
  mock_device.stub(:open)
15
15
  mock_device
16
16
  end
17
17
 
18
18
  let(:scheduler) do
19
- scheduler = mock(:scheduler)
19
+ scheduler = double(:scheduler)
20
20
  Gamelan::Scheduler.stub(:new).and_return scheduler
21
+ scheduler.stub(:stop).and_return :stop_scheduler
21
22
  scheduler
22
23
  end
23
24
 
@@ -28,14 +29,16 @@ describe MTK::IO::MIDIOutput do
28
29
  end
29
30
 
30
31
  def should_be_scheduled timed_data
32
+ explicitly_expected_stop_scheduler = false
31
33
  timed_data.each do |time,data|
34
+ explicitly_expected_stop_scheduler = true if data == :stop_scheduler
32
35
  scheduler.should_receive(:at) do |scheduled_time,&callback|
33
36
  scheduled_time.should == time
34
37
  callback.yield.should == data
35
38
  end
36
39
  end
37
- scheduler.should_receive(:at) # end time, don't care about this here...
38
- scheduler.should_receive(:run).and_return mock(:thread,:join=>nil)
40
+ scheduler.should_receive(:at) unless explicitly_expected_stop_scheduler # auto-handle stop_schedulerer if needed
41
+ scheduler.should_receive(:run).and_return double(:thread,:join=>nil)
39
42
  end
40
43
 
41
44
 
@@ -50,7 +53,8 @@ describe MTK::IO::MIDIOutput do
50
53
 
51
54
  it "handles note events" do
52
55
  should_be_scheduled 0 => [:note_on, 60, 127, 0],
53
- 1 => [:note_off, 60, 127, 0]
56
+ 1 => [:note_on, 60, 0, 0]
57
+ # 1 => [:note_off, 60, 127, 0] # for when we have proper note off support
54
58
  subject.play MTK::Events::Timeline.from_h( 0 => Note(C4,fff,1) )
55
59
  end
56
60
 
@@ -82,21 +86,50 @@ describe MTK::IO::MIDIOutput do
82
86
  it "handles simultaneous events" do
83
87
  should_be_scheduled [
84
88
  [0, [:note_on, 60, 127, 0]],
85
- [1, [:note_off, 60, 127, 0]],
89
+ [1, [:note_on, 60, 0, 0]],
90
+ # [1, [:note_off, 60, 127, 0]], # for when we have proper note off support
86
91
  [0, [:note_on, 67, 127, 0]],
87
- [1, [:note_off, 67, 127, 0]]
92
+ [1, [:note_on, 67, 0, 0]],
93
+ # [1, [:note_off, 67, 127, 0]] # for when we have proper note off support
88
94
  ]
89
95
  subject.play [Note(C4,fff,1),Note(G4,fff,1)]
90
96
  end
91
97
 
92
98
  it "handles a list of timelines" do
93
99
  should_be_scheduled 0 => [:note_on, 60, 127, 0],
94
- 1 => [:note_off, 60, 127, 0],
100
+ 1 => [:note_on, 60, 0, 0],
101
+ #1 => [:note_off, 60, 127, 0], # for when we have proper note off support
95
102
  2 => [:note_on, 67, 127, 0],
96
- 3 => [:note_off, 67, 127, 0]
103
+ 3 => [:note_on, 67, 0, 0]
104
+ #3 => [:note_off, 67, 127, 0] # for when we have proper note off support
97
105
  subject.play [ MTK::Events::Timeline.from_h( 0 => Note(C4,fff,1) ), MTK::Events::Timeline.from_h( 2 => Note(G4,fff,1) )]
98
106
  end
99
107
 
108
+
109
+ it "stops the scheduler 2 beats after the last event" do
110
+ should_be_scheduled 0 => [:note_on, 60, 127, 0],
111
+ 1 => [:note_on, 60, 0, 0],
112
+ #1 => [:note_off, 60, 127, 0], # for when we have proper note off support
113
+ 3 => :stop_scheduler
114
+ subject.play MTK::Events::Timeline.from_h( 0 => Note(C4,fff,1) )
115
+ end
116
+
117
+ it "stops the scheduler 2 beats after the longest of simultaneous final events" do
118
+ should_be_scheduled [
119
+ [0, [:note_on, 60, 127, 0]],
120
+ [1, [:note_on, 60, 0, 0]],
121
+ #[1, [:note_off, 60, 127, 0]], # for when we have proper note off support
122
+ [0, [:note_on, 62, 127, 0]],
123
+ [3.5,[:note_on, 62, 0, 0]],
124
+ #[3.5,[:note_off, 62, 127, 0]], # for when we have proper note off support
125
+ [0, [:note_on, 64, 127, 0]],
126
+ [2, [:note_on, 64, 0, 0]],
127
+ #[2, [:note_off, 64, 127, 0]], # for when we have proper note off support
128
+ [5.5, :stop_scheduler]
129
+ ]
130
+ subject.play MTK::Events::Timeline.from_h( 0 => [Note(C4,fff,1), Note(D4,fff,3.5), Note(E4,fff,2)] )
131
+ end
132
+
100
133
  end
101
134
 
102
135
  end