mtk 0.0.3.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/INTRO.md +63 -31
- data/Rakefile +3 -1
- 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
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZWM0YWVjOGJiYzM4ZjIxOWUxNWVkOWU3MDYyYWU5MDY1ZmUxMmJhOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDYwNTg1ZGMyMjMyMzBiMzljZDUyM2RkNDI4MTczMjVmY2FkNTJkMA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTRlNGRmYmEyZDBjMGRkMDYxM2U0ZGQ3YjQ5NThmZDk3MGRhNTBmYjY3MDBm
|
10
|
+
NzdiMmM0NWE2NDY0YzFkNTM1YTc4MTNhZmY1MGM5MjFiMWY3NWNjYWM0N2Nh
|
11
|
+
ZGYyMThhODA4MGJlOGNiZDIyM2EzYTFhOGY2YjgwOTNlZDNlM2U=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGNiZGVmOTAyZDM4MmVjOThmOWY0ZjE3NjgwZjRkN2M2NzY0MTRkMzZmNTk1
|
14
|
+
ZjgyMzJkMTNiODAwNzE3Y2M4MDE4MjI3YWY5MWQ1NDc2ZGY0MDlhMzg1MDMz
|
15
|
+
MjJiMjQzNzc4NjgyOTRjNDY0MzRmMjY4YzEyYjcyNTY2M2Q4MWY=
|
data/INTRO.md
CHANGED
@@ -20,9 +20,12 @@ it does not change the value in-place. For example:
|
|
20
20
|
|
21
21
|
Intensity values are intended to range from 0.0 (minimum intensity) to 1.0 (maximum intensity).
|
22
22
|
|
23
|
-
A Duration of 1 is one beat (usually a quarter note, depending on the meter
|
23
|
+
A Duration of 1 is one beat (usually a quarter note, depending on the meter, but note the quarter note value in this
|
24
|
+
library is 1 beat, so adjust accordingly for non-standard meters). By convention, negative durations
|
24
25
|
indicate a rest that lasts for the absolute value duration.
|
25
26
|
|
27
|
+
Additionally there is a core type Interval to model intervals between pitches and pitch classes.
|
28
|
+
|
26
29
|
|
27
30
|
<br/>
|
28
31
|
### Events
|
@@ -30,16 +33,23 @@ indicate a rest that lasts for the absolute value duration.
|
|
30
33
|
Events group together the core data types together to represent a musical event, like a single Note.
|
31
34
|
Events can be converted to and from MIDI.
|
32
35
|
|
33
|
-
*
|
34
|
-
*
|
36
|
+
* Event
|
37
|
+
* Note
|
38
|
+
* Parameter
|
35
39
|
|
40
|
+
Paramter represents any non-note event like a MIDI CC, pitch bend, aftertouch (pressure), etc.
|
36
41
|
|
37
|
-
|
38
|
-
|
42
|
+
Events with a negative duration are considered a rest. There is also a dedicated rest class so you
|
43
|
+
don't have to specifiy unnecessary properties like pitch for a rest.
|
44
|
+
|
45
|
+
Events are organized in time via the Timeline
|
39
46
|
|
40
|
-
A collection of timed events, such as a melody, a riff, or one track of an entire song.
|
41
47
|
|
42
|
-
|
48
|
+
<br/>
|
49
|
+
### Collections
|
50
|
+
|
51
|
+
The collection classes need some work... The interface is likely to change so it's best to not rely on them too much
|
52
|
+
right now.
|
43
53
|
|
44
54
|
|
45
55
|
<br/>
|
@@ -53,7 +63,7 @@ Structured collections of core data types and other patterns. Used to generate m
|
|
53
63
|
* line (linear interpolation between 2 points)
|
54
64
|
* function (dynamically generate elements with a lambda)
|
55
65
|
|
56
|
-
Future?
|
66
|
+
To be added in the Future?
|
57
67
|
* curve (exponential/curved interpolation between points)
|
58
68
|
* permutation (cycle that permutes itself each cycle period)
|
59
69
|
* markov chain
|
@@ -65,7 +75,23 @@ Future?
|
|
65
75
|
<br/>
|
66
76
|
### Sequencers
|
67
77
|
|
68
|
-
Convert patterns into
|
78
|
+
Convert patterns into event Timelines
|
79
|
+
|
80
|
+
The sequencer classes differ in terms of how they determine how much time should occur between one event and the next.
|
81
|
+
So far the options are:
|
82
|
+
* LegatoSequencer - the end of each event is the start of the next event
|
83
|
+
* RhythmSequencer - requires a special rhythm-type patter to determine the inter-event time intervals
|
84
|
+
* StepSequencer - uses a constant amount of time between all events. In other words, works like a drum sequencer/
|
85
|
+
|
86
|
+
|
87
|
+
<br/>
|
88
|
+
### Creating object conveniently
|
89
|
+
|
90
|
+
The Core types as well as Patterns and Note events have "convenience constructors" under the top-level MTK module.
|
91
|
+
|
92
|
+
You can construct objects from almost any arguments by using the methods such as MTK.Pitch() and MTK.Note().
|
93
|
+
These methods do their best to guess what you want from the arguments and even handle out-of-order arguments
|
94
|
+
in most cases. See the unit tests for ideas...
|
69
95
|
|
70
96
|
|
71
97
|
<br/>
|
@@ -74,56 +100,62 @@ Convert patterns into timelines
|
|
74
100
|
Basic Tenants
|
75
101
|
|
76
102
|
* Minimal syntax: less typing means more music!
|
77
|
-
*
|
78
|
-
*
|
79
|
-
* pitch is more important than rhythm
|
103
|
+
* Case-sensitive to allow for more to be expressed in fewer characters.
|
104
|
+
* Pitch and duration are the most important properties
|
80
105
|
|
81
106
|
Because pitch class and duration are such important properties, and we want to minimize typing, we represent these with 1 letter.
|
82
107
|
|
83
|
-
**Diatonic Pitch Class:
|
108
|
+
**Diatonic Pitch Class: C D E F G A B**
|
84
109
|
|
85
|
-
**Chromatic Pitch Class (Sharp/Flat):
|
110
|
+
**Chromatic Pitch Class (Sharp/Flat): C/B# C#/Db D D#/Eb E/Fb E#/F F#/Gb G G#/Ab A A#/Bb B/Cb**
|
86
111
|
|
87
|
-
Double-sharps (
|
112
|
+
Double-sharps (C##) and double-flats (Dbb) are also allowed.
|
88
113
|
|
89
|
-
**Pitch (Pitch Class + Octave Number):
|
114
|
+
**Pitch (Pitch Class + Octave Number): C4 Db5 C-1 G9**
|
90
115
|
|
91
|
-
|
116
|
+
C-1 (that's octave number: negative 1) is the lowest note. G9 is the highest.
|
117
|
+
Note: The MTK syntax expects C-1 as you'd expect, but the corresponding constant in Ruby is C_1 to ensure valid Ruby syntax.
|
92
118
|
|
93
|
-
**Duration: w h q
|
119
|
+
**Duration: w h q e s r x (that's: whole note, half, quarter, eighth, sixteenth, thirty-second, sixty-fourth)**
|
94
120
|
|
95
|
-
|
96
|
-
|
121
|
+
In the MTK syntax, durations can be suffixed with "t" or "." for triplets and dotted-notes
|
122
|
+
(mathematically that multiplies the dureation by 2/3 or 1.5).
|
97
123
|
|
98
|
-
|
124
|
+
Why "r", and "x"? We're trying to keep these one letter.
|
125
|
+
"t" is used for triplets, so the thirty-second note became "r".
|
126
|
+
"s" is used by sixteenth notes, so the sixty-fourth note became "x".
|
127
|
+
Hopefully this abnormal naming won't cause problems since those duration values are fairly uncommon.
|
99
128
|
|
100
|
-
**Intensity: ppp pp p mp mf o ff fff**
|
101
129
|
|
102
|
-
|
130
|
+
**Intensity: ppp pp p mp mf f ff fff**
|
103
131
|
|
104
132
|
|
105
133
|
TODO: keep documenting this...
|
134
|
+
TODO: explanation of intervals
|
106
135
|
|
107
136
|
|
108
137
|
Summary of single-letter assignments:
|
109
138
|
```
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
139
|
+
A -> pitch class A
|
140
|
+
B -> pitch class B
|
141
|
+
b -> flat modifier on pitch class
|
142
|
+
D -> pitch class c
|
143
|
+
D -> pitch class d
|
144
|
+
E -> pitch class e
|
145
|
+
e -> eighth note duration
|
146
|
+
F -> pitch class f
|
147
|
+
f -> forte intensity
|
116
148
|
|
117
149
|
h -> half note duration
|
118
|
-
i ->
|
150
|
+
i -> minor tonic chord (PLANNED, not implemented yet)
|
119
151
|
|
120
|
-
o -> forte intensity
|
121
152
|
p -> piano intensity
|
122
153
|
q -> quarter note duration
|
123
154
|
r -> thirty-second note duration
|
124
155
|
s -> sixteenth note duration
|
125
156
|
t -> triplet modifier on durations
|
126
157
|
|
158
|
+
v -> minor dominant chord (PLANNED, not implemented yet)
|
127
159
|
w -> whole note duration
|
128
160
|
x -> sixty-fourth note duration
|
129
161
|
```
|
data/Rakefile
CHANGED
data/bin/mtk
CHANGED
@@ -10,38 +10,38 @@ options = {}
|
|
10
10
|
|
11
11
|
option_parser = OptionParser.new do |opts|
|
12
12
|
|
13
|
-
opts.banner = "
|
13
|
+
opts.banner = "\nMTK: Music Tool Kit for Ruby\n\nUsage: #{$0} [options]"
|
14
14
|
opts.separator ''
|
15
15
|
opts.separator 'Options:'
|
16
16
|
|
17
|
-
|
18
|
-
'if
|
19
|
-
'if
|
17
|
+
opts.on('-c FILE', '--convert FILE', 'Convert file containing MTK syntax to MIDI',
|
18
|
+
'if --file is given, write the MIDI to file',
|
19
|
+
'if --output is given, play the MIDI',
|
20
20
|
'otherwise print the MIDI') {|file| options[:convert] = file }
|
21
21
|
|
22
22
|
opts.separator ''
|
23
23
|
|
24
|
-
opts.on('-e [syntax]', '--eval [syntax]', 'Convert the given MTK syntax
|
25
|
-
'
|
26
|
-
'Behaves like --convert
|
24
|
+
opts.on('-e [syntax]', '--eval [syntax]', 'Convert the given MTK syntax to MIDI or',
|
25
|
+
'interactive interpreter when no [syntax]',
|
26
|
+
'Behaves like --convert for --file/--output') {|syntax| options[:eval] = syntax }
|
27
27
|
|
28
28
|
opts.separator ''
|
29
29
|
|
30
|
-
opts.on('-f FILE', '--file FILE', 'Write
|
31
|
-
|
30
|
+
opts.on('-f FILE', '--file FILE', 'Write output of --convert, --eval, --input',
|
31
|
+
'or --watch to a file') {|file| options[:file] = file }
|
32
32
|
|
33
33
|
opts.separator ''
|
34
34
|
|
35
|
-
opts.on('-h', '--help', 'Show
|
35
|
+
opts.on('-h', '--help', 'Show these usage instructions') { puts opts; exit }
|
36
36
|
|
37
37
|
opts.separator ''
|
38
38
|
|
39
39
|
opts.on('-i INPUT', '--input INPUT', 'Set MIDI input for recording',
|
40
|
-
'if no --file
|
40
|
+
'if no --file given, prints recorded MIDI') {|input| options[:input] = input }
|
41
41
|
|
42
42
|
opts.separator ''
|
43
43
|
|
44
|
-
opts.on('-l', '--list', 'List
|
44
|
+
opts.on('-l', '--list', 'List MIDI devices for --input and --output') { options[:list] = true }
|
45
45
|
|
46
46
|
opts.separator ''
|
47
47
|
|
@@ -53,17 +53,19 @@ option_parser = OptionParser.new do |opts|
|
|
53
53
|
|
54
54
|
opts.separator ''
|
55
55
|
|
56
|
-
opts.on('-p FILE', '--play FILE', 'Play or print
|
57
|
-
'if no --output
|
56
|
+
opts.on('-p FILE', '--play FILE', 'Play or print a MIDI file',
|
57
|
+
'if no --output given, print the MIDI') {|file| options[:play] = file }
|
58
58
|
|
59
59
|
opts.separator ''
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
#opts.separator ''
|
61
|
+
opts.on('-t [color]', '--tutorial [color]', 'Interactive tutorial for MTK syntax',
|
62
|
+
'Text color can be set on/off') {|color| options[:tutorial] = true; options[:color] = color }
|
64
63
|
|
65
|
-
opts.
|
66
|
-
|
64
|
+
opts.separator ''
|
65
|
+
|
66
|
+
opts.on('-w FILE', '--watch FILE', 'Watch an MTK syntax file for changes and',
|
67
|
+
'automatically convert to MIDI',
|
68
|
+
'Behaves like --convert for --file/--output') {|file| options[:watch] = file }
|
67
69
|
|
68
70
|
end
|
69
71
|
|
@@ -71,18 +73,16 @@ end
|
|
71
73
|
#######################################################################
|
72
74
|
|
73
75
|
puts option_parser and exit if ARGV.length == 0
|
74
|
-
#p ARGV
|
75
|
-
#p options
|
76
76
|
|
77
77
|
ERROR_INVALID_COMMAND = 1
|
78
78
|
ERROR_FILE_NOT_FOUND = 2
|
79
79
|
ERROR_OUTPUT_NOT_FOUND = 3
|
80
80
|
ERROR_INPUT_NOT_FOUND = 4
|
81
81
|
|
82
|
-
#
|
83
|
-
# the first couple notes. So we play this "empty" Timeline containing a rest to address that issue.
|
82
|
+
# Empty timeline used to prime the realtime output
|
84
83
|
WARMUP = MTK::Events::Timeline.from_h( {0 => MTK.Note(60,-1)} )
|
85
84
|
|
85
|
+
|
86
86
|
#######################################################################
|
87
87
|
|
88
88
|
begin
|
@@ -115,6 +115,9 @@ end
|
|
115
115
|
def output(timelines, print_header='Timeline')
|
116
116
|
timelines = [timelines] unless timelines.is_a? Array
|
117
117
|
if @output
|
118
|
+
# Immediately trying to play output while Ruby is still "warming up" can cause timing issues with
|
119
|
+
# the first couple notes. So we play this "empty" Timeline containing a rest to address that issue.
|
120
|
+
# TODO? move this into the output class and do it automatically when playing for the first time? (warmup code is also in output_selector))
|
118
121
|
@output.play WARMUP
|
119
122
|
@output.play timelines.first # TODO: support multiple timelines
|
120
123
|
elsif @file
|
@@ -151,6 +154,33 @@ def watch_file_updated?
|
|
151
154
|
end
|
152
155
|
|
153
156
|
|
157
|
+
def set_tutorial_color(color_option)
|
158
|
+
if color_option
|
159
|
+
case color_option.strip.downcase
|
160
|
+
when /^(on|yes|true|y|t|color)$/ then $tutorial_color = true
|
161
|
+
when /^(off|no|false|n|f)$/ then $tutorial_color = false
|
162
|
+
else
|
163
|
+
STDERR.puts "Invalid tutorial color setting '#{color}'. Try 'on' or 'off'."
|
164
|
+
exit ERROR_INVALID_COMMAND
|
165
|
+
end
|
166
|
+
else
|
167
|
+
require 'rbconfig'
|
168
|
+
os = RbConfig::CONFIG['host_os'].downcase
|
169
|
+
if os =~ /win/ and os !~ /darwin/
|
170
|
+
puts
|
171
|
+
puts "Windows command line text color is off by default."
|
172
|
+
puts "If you want color, use ANSI terminal software like Cygwin or Ansicon and "
|
173
|
+
puts "run #{$0} with the color option \"--tutorial on\""
|
174
|
+
$tutorial_color = false
|
175
|
+
else
|
176
|
+
$tutorial_color = true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
puts "Tutorial color is #{if $tutorial_color then 'enabled' else 'disabled' end}."
|
180
|
+
puts
|
181
|
+
end
|
182
|
+
|
183
|
+
|
154
184
|
#######################################################################
|
155
185
|
|
156
186
|
if options[:list]
|
@@ -162,18 +192,21 @@ if options[:list]
|
|
162
192
|
puts
|
163
193
|
puts (['OUTPUTS:']+output_names).join("\n * ")
|
164
194
|
puts
|
165
|
-
puts 'When specifying --input INPUT or --output OUTPUT, the first substring match
|
166
|
-
puts
|
195
|
+
puts 'When specifying --input INPUT or --output OUTPUT, the first substring match'
|
196
|
+
puts '(case-insensitive) will be used. For example: "--output iac" will use'
|
197
|
+
puts '"Apple Inc. IAC Driver" if it\'s the first OUTPUT containing "IAC".'
|
167
198
|
puts
|
168
199
|
exit
|
169
200
|
end
|
170
201
|
|
202
|
+
|
171
203
|
@monitor = true if options[:monitor]
|
172
204
|
|
205
|
+
|
173
206
|
if options[:input]
|
174
207
|
setup_io
|
175
208
|
input_name = options[:input]
|
176
|
-
@input = MTK::IO::MIDIInput.find_by_name /#{input_name}/
|
209
|
+
@input = MTK::IO::MIDIInput.find_by_name /#{input_name}/i
|
177
210
|
if @input
|
178
211
|
puts "Using input '#{@input.name}'"
|
179
212
|
else
|
@@ -182,10 +215,11 @@ if options[:input]
|
|
182
215
|
end
|
183
216
|
end
|
184
217
|
|
218
|
+
|
185
219
|
if options[:output]
|
186
220
|
setup_io
|
187
221
|
output_name = options[:output]
|
188
|
-
@output = MTK::IO::MIDIOutput.find_by_name /#{output_name}/
|
222
|
+
@output = MTK::IO::MIDIOutput.find_by_name /#{output_name}/i
|
189
223
|
if @output
|
190
224
|
puts "Using output '#{@output.name}'"
|
191
225
|
else
|
@@ -194,8 +228,9 @@ if options[:output]
|
|
194
228
|
end
|
195
229
|
end
|
196
230
|
|
197
|
-
|
198
|
-
@file = file
|
231
|
+
|
232
|
+
@file = options[:file]
|
233
|
+
|
199
234
|
|
200
235
|
if options[:play]
|
201
236
|
filename = options[:play]
|
@@ -204,6 +239,7 @@ if options[:play]
|
|
204
239
|
output(timelines, "Timeline for #{filename}")
|
205
240
|
end
|
206
241
|
|
242
|
+
|
207
243
|
if options.has_key? :eval
|
208
244
|
mtk_syntax = options[:eval]
|
209
245
|
if mtk_syntax.nil?
|
@@ -221,12 +257,14 @@ if options.has_key? :eval
|
|
221
257
|
end
|
222
258
|
end
|
223
259
|
|
260
|
+
|
224
261
|
if options[:convert]
|
225
262
|
mtk_syntax_file = options[:convert]
|
226
263
|
mtk_syntax = IO.read(mtk_syntax_file)
|
227
264
|
convert(mtk_syntax)
|
228
265
|
end
|
229
266
|
|
267
|
+
|
230
268
|
if options[:watch]
|
231
269
|
@watch_file = options[:watch]
|
232
270
|
puts "Watching #{@watch_file}. Press Ctrl+C to exit."
|
@@ -243,8 +281,13 @@ if options[:watch]
|
|
243
281
|
end
|
244
282
|
end
|
245
283
|
|
246
|
-
|
247
|
-
|
248
|
-
|
284
|
+
|
285
|
+
if options.has_key? :tutorial
|
286
|
+
set_tutorial_color(options[:color])
|
287
|
+
require 'mtk/lang/tutorial'
|
288
|
+
tutorial = MTK::Lang::Tutorial.new
|
289
|
+
tutorial.run(@output)
|
290
|
+
end
|
291
|
+
|
249
292
|
|
250
293
|
record if @input
|
data/examples/drum_pattern.rb
CHANGED
@@ -9,8 +9,8 @@ file = ARGV[0] || "MTK-#{File.basename(__FILE__,'.rb')}.mid"
|
|
9
9
|
_ = nil # defines _ as a rest
|
10
10
|
|
11
11
|
pattern = {# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
12
|
-
C2 => [fff, _, _, _, mf, _, _, _,
|
13
|
-
Db2 => [ _, _,
|
12
|
+
C2 => [fff, _, _, _, mf, _, _, _, f, _, _, _, mp, _, _, _], # kick
|
13
|
+
Db2 => [ _, _, f, _, _, _, mp, _, _, _, f, _, _, _, mf, _], # rim shot
|
14
14
|
D2 => [ _, mp, _, mp, _, mp, _, mf, _, mp, _, mp, _, pp, _, mf] # snare
|
15
15
|
}
|
16
16
|
|
data/examples/dynamic_pattern.rb
CHANGED
@@ -29,7 +29,7 @@ end
|
|
29
29
|
pitches = Patterns.Function( interval_generator, max_elements: 24 )
|
30
30
|
|
31
31
|
# we'll also use a weighted choice to generate the intensities
|
32
|
-
intensities = Patterns.Choice( mp,mf,
|
32
|
+
intensities = Patterns.Choice( mp,mf,f,ff,fff, weights: [1,2,3,2,1], max_cycles: 24 )
|
33
33
|
|
34
34
|
sequencer = Sequencers.StepSequencer( pitches,intensities, step_size: 0.5, max_interval: 17 )
|
35
35
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'mtk/io/midi_output'
|
2
|
+
|
3
|
+
# Assists with selecting an output.
|
4
|
+
class OutputSelector
|
5
|
+
|
6
|
+
# Empty timeline used to prime the realtime output
|
7
|
+
WARMUP = MTK::Events::Timeline.from_h( {0 => MTK.Note(60,-1)} )
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def output
|
12
|
+
MTK::IO::MIDIOutput
|
13
|
+
end
|
14
|
+
|
15
|
+
# Look for an output by name using case insensitive matching,
|
16
|
+
# treating underscore like either an underscore or whitespace
|
17
|
+
def search output_name_pattern
|
18
|
+
output.find_by_name(/#{output_name_pattern.to_s.sub '_','(_|\\s+)'}/i)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Command line interface to list output choices and select an output.
|
22
|
+
def prompt_for_output
|
23
|
+
devices_by_name = output.devices_by_name
|
24
|
+
names_by_number = {}
|
25
|
+
|
26
|
+
puts "Available MIDI outputs:"
|
27
|
+
devices_by_name.keys.each_with_index do |name,index|
|
28
|
+
number = index+1
|
29
|
+
names_by_number[number] = name
|
30
|
+
puts " #{number} => #{name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
print "Enter the number of the output to test: "
|
34
|
+
device = nil
|
35
|
+
loop do
|
36
|
+
begin
|
37
|
+
# NOTE: invalid input will turn into 0, but that's ok because we index from 1 so it will be caught as invalid
|
38
|
+
number = STDIN.gets.to_i
|
39
|
+
name = names_by_number[number]
|
40
|
+
device = devices_by_name[name]
|
41
|
+
return output.open(device) if device
|
42
|
+
rescue
|
43
|
+
if $DEBUG
|
44
|
+
puts $!
|
45
|
+
puts $!.backtrace
|
46
|
+
end
|
47
|
+
# keep looping
|
48
|
+
end
|
49
|
+
print "Invalid input. Enter a number listed above: "
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def ensure_output name=nil
|
55
|
+
output = nil
|
56
|
+
if name
|
57
|
+
output = search name
|
58
|
+
puts "Output '#{name}' not found." unless output
|
59
|
+
end
|
60
|
+
output ||= prompt_for_output
|
61
|
+
|
62
|
+
# Immediately trying to play output while Ruby is still "warming up" can cause timing issues with
|
63
|
+
# the first couple notes. So we play this "empty" Timeline containing a rest to address that issue.
|
64
|
+
output.play WARMUP
|
65
|
+
|
66
|
+
output
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|