mtk 0.0.3.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
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