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.
- checksums.yaml +15 -0
- data/DEVELOPMENT_NOTES.md +20 -0
- data/INTRO.md +63 -31
- data/README.md +9 -3
- data/Rakefile +42 -42
- data/bin/jmtk +75 -32
- data/bin/mtk +75 -32
- data/examples/drum_pattern.rb +2 -2
- data/examples/dynamic_pattern.rb +1 -1
- data/examples/helpers/output_selector.rb +71 -0
- data/examples/notation.rb +5 -1
- data/examples/tone_row_melody.rb +1 -1
- data/lib/mtk.rb +1 -0
- data/lib/mtk/core/duration.rb +18 -3
- data/lib/mtk/core/intensity.rb +5 -3
- data/lib/mtk/core/interval.rb +21 -14
- data/lib/mtk/core/pitch.rb +2 -0
- data/lib/mtk/core/pitch_class.rb +6 -3
- data/lib/mtk/events/event.rb +2 -1
- data/lib/mtk/events/note.rb +1 -1
- data/lib/mtk/events/parameter.rb +1 -0
- data/lib/mtk/events/rest.rb +85 -0
- data/lib/mtk/events/timeline.rb +6 -2
- data/lib/mtk/io/jsound_input.rb +9 -3
- data/lib/mtk/io/midi_file.rb +38 -2
- data/lib/mtk/io/midi_input.rb +1 -1
- data/lib/mtk/io/midi_output.rb +95 -4
- data/lib/mtk/io/unimidi_input.rb +7 -3
- data/lib/mtk/lang/durations.rb +31 -26
- data/lib/mtk/lang/intensities.rb +29 -30
- data/lib/mtk/lang/intervals.rb +108 -41
- data/lib/mtk/lang/mtk_grammar.citrus +14 -4
- data/lib/mtk/lang/parser.rb +10 -5
- data/lib/mtk/lang/pitch_classes.rb +45 -17
- data/lib/mtk/lang/pitches.rb +169 -32
- data/lib/mtk/lang/tutorial.rb +279 -0
- data/lib/mtk/lang/tutorial_lesson.rb +87 -0
- data/lib/mtk/sequencers/event_builder.rb +29 -8
- data/spec/mtk/core/duration_spec.rb +14 -1
- data/spec/mtk/core/intensity_spec.rb +1 -1
- data/spec/mtk/events/event_spec.rb +10 -16
- data/spec/mtk/events/note_spec.rb +3 -3
- data/spec/mtk/events/rest_spec.rb +184 -0
- data/spec/mtk/events/timeline_spec.rb +5 -1
- data/spec/mtk/io/midi_file_spec.rb +13 -2
- data/spec/mtk/io/midi_output_spec.rb +42 -9
- data/spec/mtk/lang/durations_spec.rb +5 -5
- data/spec/mtk/lang/intensities_spec.rb +5 -5
- data/spec/mtk/lang/intervals_spec.rb +139 -13
- data/spec/mtk/lang/parser_spec.rb +65 -25
- data/spec/mtk/lang/pitch_classes_spec.rb +0 -11
- data/spec/mtk/lang/pitches_spec.rb +0 -15
- data/spec/mtk/patterns/chain_spec.rb +7 -7
- data/spec/mtk/patterns/for_each_spec.rb +2 -2
- data/spec/mtk/sequencers/event_builder_spec.rb +49 -17
- 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
|
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
|
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
|
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
|
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
|
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
|
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
|
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::
|
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,
|
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 =
|
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 =
|
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)
|
38
|
-
scheduler.should_receive(:run).and_return
|
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 => [:
|
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, [:
|
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, [:
|
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 => [:
|
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 => [:
|
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
|