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.
- 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
|