mtk 0.0.3.3 → 0.4

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 (53) hide show
  1. checksums.yaml +15 -0
  2. data/INTRO.md +63 -31
  3. data/Rakefile +3 -1
  4. data/bin/mtk +75 -32
  5. data/examples/drum_pattern.rb +2 -2
  6. data/examples/dynamic_pattern.rb +1 -1
  7. data/examples/helpers/output_selector.rb +71 -0
  8. data/examples/notation.rb +5 -1
  9. data/examples/tone_row_melody.rb +1 -1
  10. data/lib/mtk.rb +1 -0
  11. data/lib/mtk/core/duration.rb +18 -3
  12. data/lib/mtk/core/intensity.rb +5 -3
  13. data/lib/mtk/core/interval.rb +21 -14
  14. data/lib/mtk/core/pitch.rb +2 -0
  15. data/lib/mtk/core/pitch_class.rb +6 -3
  16. data/lib/mtk/events/event.rb +2 -1
  17. data/lib/mtk/events/note.rb +1 -1
  18. data/lib/mtk/events/parameter.rb +1 -0
  19. data/lib/mtk/events/rest.rb +85 -0
  20. data/lib/mtk/events/timeline.rb +6 -2
  21. data/lib/mtk/io/jsound_input.rb +9 -3
  22. data/lib/mtk/io/midi_file.rb +38 -2
  23. data/lib/mtk/io/midi_input.rb +1 -1
  24. data/lib/mtk/io/midi_output.rb +95 -4
  25. data/lib/mtk/io/unimidi_input.rb +7 -3
  26. data/lib/mtk/lang/durations.rb +31 -26
  27. data/lib/mtk/lang/intensities.rb +29 -30
  28. data/lib/mtk/lang/intervals.rb +108 -41
  29. data/lib/mtk/lang/mtk_grammar.citrus +14 -4
  30. data/lib/mtk/lang/parser.rb +10 -5
  31. data/lib/mtk/lang/pitch_classes.rb +45 -17
  32. data/lib/mtk/lang/pitches.rb +169 -32
  33. data/lib/mtk/lang/tutorial.rb +279 -0
  34. data/lib/mtk/lang/tutorial_lesson.rb +87 -0
  35. data/lib/mtk/sequencers/event_builder.rb +29 -8
  36. data/spec/mtk/core/duration_spec.rb +14 -1
  37. data/spec/mtk/core/intensity_spec.rb +1 -1
  38. data/spec/mtk/events/event_spec.rb +10 -16
  39. data/spec/mtk/events/note_spec.rb +3 -3
  40. data/spec/mtk/events/rest_spec.rb +184 -0
  41. data/spec/mtk/events/timeline_spec.rb +5 -1
  42. data/spec/mtk/io/midi_file_spec.rb +13 -2
  43. data/spec/mtk/io/midi_output_spec.rb +42 -9
  44. data/spec/mtk/lang/durations_spec.rb +5 -5
  45. data/spec/mtk/lang/intensities_spec.rb +5 -5
  46. data/spec/mtk/lang/intervals_spec.rb +139 -13
  47. data/spec/mtk/lang/parser_spec.rb +65 -25
  48. data/spec/mtk/lang/pitch_classes_spec.rb +0 -11
  49. data/spec/mtk/lang/pitches_spec.rb +0 -15
  50. data/spec/mtk/patterns/chain_spec.rb +7 -7
  51. data/spec/mtk/patterns/for_each_spec.rb +2 -2
  52. data/spec/mtk/sequencers/event_builder_spec.rb +49 -17
  53. metadata +12 -22
@@ -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
@@ -29,12 +29,12 @@ describe MTK::Lang::Durations do
29
29
  end
30
30
  end
31
31
 
32
- describe 'i' do
32
+ describe 'e' do
33
33
  it 'is 1/2 of a beat' do
34
- i.value.should == 1.0/2
34
+ e.value.should == 1.0/2
35
35
  end
36
36
  it 'is available via a module property and via mixin' do
37
- Durations::i.should == i
37
+ Durations::e.should == e
38
38
  end
39
39
  end
40
40
 
@@ -67,7 +67,7 @@ describe MTK::Lang::Durations do
67
67
 
68
68
  describe "DURATIONS" do
69
69
  it "contains all Durations pseudo-constants" do
70
- Durations::DURATIONS.should =~ [w, h, q, i, s, r, x]
70
+ Durations::DURATIONS.should =~ [w, h, q, e, s, r, x]
71
71
  end
72
72
 
73
73
  it "is immutable" do
@@ -77,7 +77,7 @@ describe MTK::Lang::Durations do
77
77
 
78
78
  describe "DURATION_NAMES" do
79
79
  it "contains all Durations pseudo-constants names as strings" do
80
- Durations::DURATION_NAMES.should =~ ['w', 'h', 'q', 'i', 's', 'r', 'x']
80
+ Durations::DURATION_NAMES.should =~ ['w', 'h', 'q', 'e', 's', 'r', 'x']
81
81
  end
82
82
 
83
83
  it "is immutable" do
@@ -47,12 +47,12 @@ describe MTK::Lang::Intensities do
47
47
  end
48
48
  end
49
49
 
50
- describe 'o' do # AKA forte
50
+ describe 'f' do # AKA forte
51
51
  it 'is equivalent to MIDI velocity 95' do
52
- (o.value * 127).round.should == 95
52
+ (f.value * 127).round.should == 95
53
53
  end
54
54
  it 'is available via a module property and via mixin' do
55
- Intensities::o.should == o
55
+ Intensities::f.should == f
56
56
  end
57
57
  it "does not overwrite the PitchClass constant 'F'" do
58
58
  F.should be_a PitchClass
@@ -79,7 +79,7 @@ describe MTK::Lang::Intensities do
79
79
 
80
80
  describe "INTENSITIES" do
81
81
  it "contains all Intensities pseudo-constants" do
82
- Intensities::INTENSITIES.should =~ [ppp, pp, p, mp, mf, o, ff, fff]
82
+ Intensities::INTENSITIES.should =~ [ppp, pp, p, mp, mf, f, ff, fff]
83
83
  end
84
84
 
85
85
  it "is immutable" do
@@ -89,7 +89,7 @@ describe MTK::Lang::Intensities do
89
89
 
90
90
  describe "INTENSITY_NAMES" do
91
91
  it "contains all Intensities pseudo-constants names as strings" do
92
- Intensities::INTENSITY_NAMES.should =~ ['ppp', 'pp', 'p', 'mp', 'mf', 'o', 'ff', 'fff']
92
+ Intensities::INTENSITY_NAMES.should =~ ['ppp', 'pp', 'p', 'mp', 'mf', 'f', 'ff', 'fff']
93
93
  end
94
94
 
95
95
  it "is immutable" do
@@ -11,12 +11,30 @@ describe MTK::Lang::Intervals do
11
11
  end
12
12
  end
13
13
 
14
+ describe 'd2' do
15
+ it 'is 0 semitones' do
16
+ d2.should == Interval[0]
17
+ end
18
+ it 'is available via a module property and via mixin' do
19
+ Intervals::d2.should == d2
20
+ end
21
+ end
22
+
14
23
  describe 'm2' do
15
24
  it 'is 1 semitone' do
16
25
  m2.should == Interval[1]
17
26
  end
18
27
  it 'is available via a module property and via mixin' do
19
- Intervals::P1.should == P1
28
+ Intervals::m2.should == m2
29
+ end
30
+ end
31
+
32
+ describe 'a1' do
33
+ it 'is 1 semitone' do
34
+ a1.should == Interval[1]
35
+ end
36
+ it 'is available via a module property and via mixin' do
37
+ Intervals::a1.should == a1
20
38
  end
21
39
  end
22
40
 
@@ -25,7 +43,16 @@ describe MTK::Lang::Intervals do
25
43
  M2.should == Interval[2]
26
44
  end
27
45
  it 'is available via a module property and via mixin' do
28
- Intervals::P1.should == P1
46
+ Intervals::M2.should == M2
47
+ end
48
+ end
49
+
50
+ describe 'd3' do
51
+ it 'is 2 semitones' do
52
+ d3.should == Interval[2]
53
+ end
54
+ it 'is available via a module property and via mixin' do
55
+ Intervals::d3.should == d3
29
56
  end
30
57
  end
31
58
 
@@ -34,7 +61,16 @@ describe MTK::Lang::Intervals do
34
61
  m3.should == Interval[3]
35
62
  end
36
63
  it 'is available via a module property and via mixin' do
37
- Intervals::P1.should == P1
64
+ Intervals::m3.should == m3
65
+ end
66
+ end
67
+
68
+ describe 'a2' do
69
+ it 'is 3 semitones' do
70
+ a2.should == Interval[3]
71
+ end
72
+ it 'is available via a module property and via mixin' do
73
+ Intervals::a2.should == a2
38
74
  end
39
75
  end
40
76
 
@@ -43,7 +79,16 @@ describe MTK::Lang::Intervals do
43
79
  M3.should == Interval[4]
44
80
  end
45
81
  it 'is available via a module property and via mixin' do
46
- Intervals::P1.should == P1
82
+ Intervals::M3.should == M3
83
+ end
84
+ end
85
+
86
+ describe 'd4' do
87
+ it 'is 4 semitones' do
88
+ d4.should == Interval[4]
89
+ end
90
+ it 'is available via a module property and via mixin' do
91
+ Intervals::d4.should == d4
47
92
  end
48
93
  end
49
94
 
@@ -52,7 +97,16 @@ describe MTK::Lang::Intervals do
52
97
  P4.should == Interval[5]
53
98
  end
54
99
  it 'is available via a module property and via mixin' do
55
- Intervals::P1.should == P1
100
+ Intervals::P4.should == P4
101
+ end
102
+ end
103
+
104
+ describe 'a3' do
105
+ it 'is 5 semitones' do
106
+ a3.should == Interval[5]
107
+ end
108
+ it 'is available via a module property and via mixin' do
109
+ Intervals::a3.should == a3
56
110
  end
57
111
  end
58
112
 
@@ -61,7 +115,25 @@ describe MTK::Lang::Intervals do
61
115
  TT.should == Interval[6]
62
116
  end
63
117
  it 'is available via a module property and via mixin' do
64
- Intervals::P1.should == P1
118
+ Intervals::TT.should == TT
119
+ end
120
+ end
121
+
122
+ describe 'a4' do
123
+ it 'is 6 semitones' do
124
+ a4.should == Interval[6]
125
+ end
126
+ it 'is available via a module property and via mixin' do
127
+ Intervals::a4.should == a4
128
+ end
129
+ end
130
+
131
+ describe 'd5' do
132
+ it 'is 6 semitones' do
133
+ d5.should == Interval[6]
134
+ end
135
+ it 'is available via a module property and via mixin' do
136
+ Intervals::d5.should == d5
65
137
  end
66
138
  end
67
139
 
@@ -70,7 +142,16 @@ describe MTK::Lang::Intervals do
70
142
  P5.should == Interval[7]
71
143
  end
72
144
  it 'is available via a module property and via mixin' do
73
- Intervals::P1.should == P1
145
+ Intervals::P5.should == P5
146
+ end
147
+ end
148
+
149
+ describe 'd6' do
150
+ it 'is 7 semitones' do
151
+ d6.should == Interval[7]
152
+ end
153
+ it 'is available via a module property and via mixin' do
154
+ Intervals::d6.should == d6
74
155
  end
75
156
  end
76
157
 
@@ -83,12 +164,30 @@ describe MTK::Lang::Intervals do
83
164
  end
84
165
  end
85
166
 
167
+ describe 'a5' do
168
+ it 'is 8 semitones' do
169
+ a5.should == Interval[8]
170
+ end
171
+ it 'is available via a module property and via mixin' do
172
+ Intervals::a5.should == a5
173
+ end
174
+ end
175
+
86
176
  describe 'M6' do
87
177
  it 'is 9 semitones' do
88
178
  M6.should == Interval[9]
89
179
  end
90
180
  it 'is available via a module property and via mixin' do
91
- Intervals::P1.should == P1
181
+ Intervals::M6.should == M6
182
+ end
183
+ end
184
+
185
+ describe 'd7' do
186
+ it 'is 9 semitones' do
187
+ d7.should == Interval[9]
188
+ end
189
+ it 'is available via a module property and via mixin' do
190
+ Intervals::d7.should == d7
92
191
  end
93
192
  end
94
193
 
@@ -97,7 +196,16 @@ describe MTK::Lang::Intervals do
97
196
  m7.should == Interval[10]
98
197
  end
99
198
  it 'is available via a module property and via mixin' do
100
- Intervals::P1.should == P1
199
+ Intervals::m7.should == m7
200
+ end
201
+ end
202
+
203
+ describe 'a6' do
204
+ it 'is 10 semitones' do
205
+ a6.should == Interval[10]
206
+ end
207
+ it 'is available via a module property and via mixin' do
208
+ Intervals::a6.should == a6
101
209
  end
102
210
  end
103
211
 
@@ -106,7 +214,16 @@ describe MTK::Lang::Intervals do
106
214
  M7.should == Interval[11]
107
215
  end
108
216
  it 'is available via a module property and via mixin' do
109
- Intervals::P1.should == P1
217
+ Intervals::M7.should == M7
218
+ end
219
+ end
220
+
221
+ describe 'd8' do
222
+ it 'is 11 semitones' do
223
+ d8.should == Interval[11]
224
+ end
225
+ it 'is available via a module property and via mixin' do
226
+ Intervals::d8.should == d8
110
227
  end
111
228
  end
112
229
 
@@ -115,14 +232,23 @@ describe MTK::Lang::Intervals do
115
232
  P8.should == Interval[12]
116
233
  end
117
234
  it 'is available via a module property and via mixin' do
118
- Intervals::P1.should == P1
235
+ Intervals::P8.should == P8
236
+ end
237
+ end
238
+
239
+ describe 'a7' do
240
+ it 'is 12 semitones' do
241
+ a7.should == Interval[12]
242
+ end
243
+ it 'is available via a module property and via mixin' do
244
+ Intervals::a7.should == a7
119
245
  end
120
246
  end
121
247
 
122
248
 
123
249
  describe "INTERVALS" do
124
250
  it "contains all intervals constants/pseudo-constants" do
125
- Intervals::INTERVALS.should =~ [P1, m2, M2, m3, M3, P4, TT, P5, m6, M6, m7, M7, P8]
251
+ Intervals::INTERVALS.should =~ MTK::Core::Interval::ALL_NAMES.map{|name| MTK::Core::Interval.from_name(name) }
126
252
  end
127
253
 
128
254
  it "is immutable" do
@@ -132,7 +258,7 @@ describe MTK::Lang::Intervals do
132
258
 
133
259
  describe "INTERVAL_NAMES" do
134
260
  it "contains all intervals constants/pseudo-constant names" do
135
- Intervals::INTERVAL_NAMES.should =~ ['P1', 'm2', 'M2', 'm3', 'M3', 'P4', 'TT', 'P5', 'm6', 'M6', 'm7', 'M7', 'P8']
261
+ Intervals::INTERVAL_NAMES.should =~ MTK::Core::Interval::ALL_NAMES
136
262
  end
137
263
 
138
264
  it "is immutable" do