jmtk 0.0.3.3-java → 0.4-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 (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