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
@@ -133,25 +133,35 @@ grammar MTK_Grammar
|
|
133
133
|
end
|
134
134
|
|
135
135
|
rule pitch_class
|
136
|
-
(
|
136
|
+
( diatonic_pitch_class accidental? ) {
|
137
137
|
MTK::Core::PitchClass[to_s]
|
138
138
|
}
|
139
139
|
end
|
140
140
|
|
141
|
+
rule diatonic_pitch_class
|
142
|
+
( [A-G] ) {
|
143
|
+
MTK::Core::PitchClass[to_s]
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
rule accidental
|
148
|
+
('#'1*2 | 'b'1*2)
|
149
|
+
end
|
150
|
+
|
141
151
|
rule interval
|
142
|
-
(
|
152
|
+
( 'P' [1458] | [Mm] [2367] | 'a' [1-7] | 'd' [2-8] | 'TT' ) {
|
143
153
|
MTK::Core::Interval.from_s(to_s)
|
144
154
|
}
|
145
155
|
end
|
146
156
|
|
147
157
|
rule intensity
|
148
|
-
( ('p'1*3 | 'mp' | 'mf' | '
|
158
|
+
( ('p'1*3 | 'mp' | 'mf' | 'f'1*3) ('+'|'-')? ) {
|
149
159
|
MTK::Core::Intensity.from_s(to_s)
|
150
160
|
}
|
151
161
|
end
|
152
162
|
|
153
163
|
rule duration
|
154
|
-
( rest:'-'? multiplier:number? [
|
164
|
+
( rest:'-'? multiplier:number? [whqesrx] ('.'|'t')* ) {
|
155
165
|
MTK::Core::Duration.from_s(to_s)
|
156
166
|
}
|
157
167
|
end
|
data/lib/mtk/lang/parser.rb
CHANGED
@@ -12,15 +12,20 @@ Citrus.load File.join(File.dirname(__FILE__),'mtk_grammar')
|
|
12
12
|
module MTK
|
13
13
|
module Lang
|
14
14
|
|
15
|
-
# Parser for the {file:lib/mtk/lang/mtk_grammar.citrus MTK
|
15
|
+
# Parser for the {file:lib/mtk/lang/mtk_grammar.citrus MTK syntax}
|
16
16
|
class Parser
|
17
17
|
|
18
|
-
|
18
|
+
# Parse the given MTK syntax according to the {file:lib/mtk/lang/mtk_grammar.citrus grammar rules}
|
19
|
+
# @return [Sequencers::LegatoSequencer] by default
|
20
|
+
# @return [Core,Patterns,Sequencers] a core object, pattern or sequencer when an optional grammar rule
|
21
|
+
# is given. Depends on the rule.
|
22
|
+
# @raise [Citrus::ParseError] for invalid syntax
|
23
|
+
def self.parse(syntax, grammar_rule=:root, dump=false)
|
19
24
|
syntax = syntax.to_s.strip
|
20
25
|
return nil if syntax.empty?
|
21
|
-
|
22
|
-
puts
|
23
|
-
|
26
|
+
match = ::MTK_Grammar.parse(syntax, root: grammar_rule)
|
27
|
+
puts match.dump if dump
|
28
|
+
match.value
|
24
29
|
end
|
25
30
|
|
26
31
|
end
|
@@ -1,29 +1,57 @@
|
|
1
1
|
module MTK
|
2
2
|
module Lang
|
3
3
|
|
4
|
-
# Defines a constant for each {PitchClass} in the Western chromatic scale.
|
4
|
+
# Defines a constant for each {Core::PitchClass} in the Western chromatic scale.
|
5
|
+
#
|
6
|
+
# Because '#' is not a valid identifier character in Ruby. All chromatic pitch classes are defined as
|
7
|
+
# the flat of a diatonic pitch class, for example Eb is a constant because D# is not a valid Ruby constant name.
|
8
|
+
#
|
9
|
+
# To help automate the documentation, the constants are listed under "Instance Attribute Summary" on this page.
|
10
|
+
#
|
11
|
+
# @see Core::PitchClass
|
12
|
+
#
|
5
13
|
module PitchClasses
|
6
14
|
|
7
|
-
#
|
8
|
-
|
15
|
+
# @private
|
16
|
+
# @!macro [attach] define_pitch_class
|
17
|
+
# PitchClass $1 $3
|
18
|
+
# @!attribute [r]
|
19
|
+
# @return [MTK::Core::PitchClass] PitchClass $1 (value $2)
|
20
|
+
def self.define_pitch_class name, value, more_info=nil
|
21
|
+
const_set name, MTK::Core::PitchClass.from_name(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
define_pitch_class 'C', 0
|
25
|
+
|
26
|
+
define_pitch_class 'Db', 1, '(also known as C#)'
|
27
|
+
|
28
|
+
define_pitch_class 'D', 2
|
29
|
+
|
30
|
+
define_pitch_class 'Eb', 3, '(also known as D#)'
|
31
|
+
|
32
|
+
define_pitch_class 'E', 4
|
33
|
+
|
34
|
+
define_pitch_class 'F', 5
|
35
|
+
|
36
|
+
define_pitch_class 'Gb', 6, '(also known as F#)'
|
37
|
+
|
38
|
+
define_pitch_class 'G', 7
|
39
|
+
|
40
|
+
define_pitch_class 'Ab', 8, '(also known as G#)'
|
41
|
+
|
42
|
+
define_pitch_class 'A', 9
|
43
|
+
|
44
|
+
define_pitch_class 'Bb', 10, '(also known as A#)'
|
45
|
+
|
46
|
+
define_pitch_class 'B', 11
|
47
|
+
|
48
|
+
# All constants defined in this module
|
49
|
+
PITCH_CLASSES = [C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B].freeze
|
9
50
|
|
10
51
|
# The names of all constants defined in this module
|
52
|
+
# @see MTK::Core::PitchClass::NAMES
|
11
53
|
PITCH_CLASS_NAMES = MTK::Core::PitchClass::NAMES
|
12
54
|
|
13
|
-
PITCH_CLASSES.each { |pc| const_set pc.name, pc }
|
14
|
-
|
15
|
-
# Lookup the value of an pitch class constant by name.
|
16
|
-
# @example lookup value of 'C'
|
17
|
-
# MTK::Core::PitchClasses['C']
|
18
|
-
# @see Groups::PitchClass.[]
|
19
|
-
def self.[](name)
|
20
|
-
begin
|
21
|
-
const_get name
|
22
|
-
rescue
|
23
|
-
nil
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
55
|
end
|
28
56
|
end
|
29
57
|
end
|
data/lib/mtk/lang/pitches.rb
CHANGED
@@ -2,51 +2,188 @@ module MTK
|
|
2
2
|
module Lang
|
3
3
|
|
4
4
|
# Defines a constants for each {Core::Pitch} in the standard MIDI range using scientific pitch notation.
|
5
|
+
# The constants range from C-1 (MIDI value 0) to G9 (MIDI value)
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Because the character '#' cannot be used in the name of a constant,
|
8
|
+
# the "black key" pitches are all named as flats with 'b' (for example, Gb3 or Db4).
|
9
|
+
# And because the character '-' (minus) cannot be used in the name of a constant,
|
10
|
+
# the low pitches use '_' (underscore) in place of '-' (minus) (for example C_1).
|
7
11
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# @
|
11
|
-
#
|
12
|
-
|
12
|
+
# To help automate the documentation, the constants are listed under "Instance Attribute Summary" on this page.
|
13
|
+
#
|
14
|
+
# @see Core::Pitch
|
15
|
+
# @see Events::Note
|
16
|
+
# @see http://en.wikipedia.org/wiki/Scientific_pitch_notation
|
17
|
+
module Pitches
|
18
|
+
|
19
|
+
# @private
|
20
|
+
# @!macro [attach] define_pitch
|
21
|
+
# Pitch $1 (MIDI pitch $2)
|
22
|
+
# @!attribute [r]
|
23
|
+
# @return [MTK::Core::Pitch] Pitch $1 (value $2)
|
24
|
+
def self.define_pitch name, value
|
25
|
+
pitch = MTK::Core::Pitch.from_i(value)
|
26
|
+
const_set name, pitch
|
27
|
+
PITCHES << pitch
|
28
|
+
PITCH_NAMES << name
|
29
|
+
end
|
13
30
|
|
14
31
|
# The values of all constants defined in this module
|
32
|
+
# @note This is populated dynamically so the documentation does not reflect the actual value
|
15
33
|
PITCHES = []
|
16
34
|
|
17
35
|
# The names of all constants defined in this module
|
36
|
+
# @note This is populated dynamically so the documentation does not reflect the actual value
|
18
37
|
PITCH_NAMES = []
|
19
38
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
define_pitch 'C_1', 0
|
40
|
+
define_pitch 'Db_1', 1
|
41
|
+
define_pitch 'D_1', 2
|
42
|
+
define_pitch 'Eb_1', 3
|
43
|
+
define_pitch 'E_1', 4
|
44
|
+
define_pitch 'F_1', 5
|
45
|
+
define_pitch 'Gb_1', 6
|
46
|
+
define_pitch 'G_1', 7
|
47
|
+
define_pitch 'Ab_1', 8
|
48
|
+
define_pitch 'A_1', 9
|
49
|
+
define_pitch 'Bb_1', 10
|
50
|
+
define_pitch 'B_1', 11
|
51
|
+
define_pitch 'C0', 12
|
52
|
+
define_pitch 'Db0', 13
|
53
|
+
define_pitch 'D0', 14
|
54
|
+
define_pitch 'Eb0', 15
|
55
|
+
define_pitch 'E0', 16
|
56
|
+
define_pitch 'F0', 17
|
57
|
+
define_pitch 'Gb0', 18
|
58
|
+
define_pitch 'G0', 19
|
59
|
+
define_pitch 'Ab0', 20
|
60
|
+
define_pitch 'A0', 21
|
61
|
+
define_pitch 'Bb0', 22
|
62
|
+
define_pitch 'B0', 23
|
63
|
+
define_pitch 'C1', 24
|
64
|
+
define_pitch 'Db1', 25
|
65
|
+
define_pitch 'D1', 26
|
66
|
+
define_pitch 'Eb1', 27
|
67
|
+
define_pitch 'E1', 28
|
68
|
+
define_pitch 'F1', 29
|
69
|
+
define_pitch 'Gb1', 30
|
70
|
+
define_pitch 'G1', 31
|
71
|
+
define_pitch 'Ab1', 32
|
72
|
+
define_pitch 'A1', 33
|
73
|
+
define_pitch 'Bb1', 34
|
74
|
+
define_pitch 'B1', 35
|
75
|
+
define_pitch 'C2', 36
|
76
|
+
define_pitch 'Db2', 37
|
77
|
+
define_pitch 'D2', 38
|
78
|
+
define_pitch 'Eb2', 39
|
79
|
+
define_pitch 'E2', 40
|
80
|
+
define_pitch 'F2', 41
|
81
|
+
define_pitch 'Gb2', 42
|
82
|
+
define_pitch 'G2', 43
|
83
|
+
define_pitch 'Ab2', 44
|
84
|
+
define_pitch 'A2', 45
|
85
|
+
define_pitch 'Bb2', 46
|
86
|
+
define_pitch 'B2', 47
|
87
|
+
define_pitch 'C3', 48
|
88
|
+
define_pitch 'Db3', 49
|
89
|
+
define_pitch 'D3', 50
|
90
|
+
define_pitch 'Eb3', 51
|
91
|
+
define_pitch 'E3', 52
|
92
|
+
define_pitch 'F3', 53
|
93
|
+
define_pitch 'Gb3', 54
|
94
|
+
define_pitch 'G3', 55
|
95
|
+
define_pitch 'Ab3', 56
|
96
|
+
define_pitch 'A3', 57
|
97
|
+
define_pitch 'Bb3', 58
|
98
|
+
define_pitch 'B3', 59
|
99
|
+
define_pitch 'C4', 60
|
100
|
+
define_pitch 'Db4', 61
|
101
|
+
define_pitch 'D4', 62
|
102
|
+
define_pitch 'Eb4', 63
|
103
|
+
define_pitch 'E4', 64
|
104
|
+
define_pitch 'F4', 65
|
105
|
+
define_pitch 'Gb4', 66
|
106
|
+
define_pitch 'G4', 67
|
107
|
+
define_pitch 'Ab4', 68
|
108
|
+
define_pitch 'A4', 69
|
109
|
+
define_pitch 'Bb4', 70
|
110
|
+
define_pitch 'B4', 71
|
111
|
+
define_pitch 'C5', 72
|
112
|
+
define_pitch 'Db5', 73
|
113
|
+
define_pitch 'D5', 74
|
114
|
+
define_pitch 'Eb5', 75
|
115
|
+
define_pitch 'E5', 76
|
116
|
+
define_pitch 'F5', 77
|
117
|
+
define_pitch 'Gb5', 78
|
118
|
+
define_pitch 'G5', 79
|
119
|
+
define_pitch 'Ab5', 80
|
120
|
+
define_pitch 'A5', 81
|
121
|
+
define_pitch 'Bb5', 82
|
122
|
+
define_pitch 'B5', 83
|
123
|
+
define_pitch 'C6', 84
|
124
|
+
define_pitch 'Db6', 85
|
125
|
+
define_pitch 'D6', 86
|
126
|
+
define_pitch 'Eb6', 87
|
127
|
+
define_pitch 'E6', 88
|
128
|
+
define_pitch 'F6', 89
|
129
|
+
define_pitch 'Gb6', 90
|
130
|
+
define_pitch 'G6', 91
|
131
|
+
define_pitch 'Ab6', 92
|
132
|
+
define_pitch 'A6', 93
|
133
|
+
define_pitch 'Bb6', 94
|
134
|
+
define_pitch 'B6', 95
|
135
|
+
define_pitch 'C7', 96
|
136
|
+
define_pitch 'Db7', 97
|
137
|
+
define_pitch 'D7', 98
|
138
|
+
define_pitch 'Eb7', 99
|
139
|
+
define_pitch 'E7', 100
|
140
|
+
define_pitch 'F7', 101
|
141
|
+
define_pitch 'Gb7', 102
|
142
|
+
define_pitch 'G7', 103
|
143
|
+
define_pitch 'Ab7', 104
|
144
|
+
define_pitch 'A7', 105
|
145
|
+
define_pitch 'Bb7', 106
|
146
|
+
define_pitch 'B7', 107
|
147
|
+
define_pitch 'C8', 108
|
148
|
+
define_pitch 'Db8', 109
|
149
|
+
define_pitch 'D8', 110
|
150
|
+
define_pitch 'Eb8', 111
|
151
|
+
define_pitch 'E8', 112
|
152
|
+
define_pitch 'F8', 113
|
153
|
+
define_pitch 'Gb8', 114
|
154
|
+
define_pitch 'G8', 115
|
155
|
+
define_pitch 'Ab8', 116
|
156
|
+
define_pitch 'A8', 117
|
157
|
+
define_pitch 'Bb8', 118
|
158
|
+
define_pitch 'B8', 119
|
159
|
+
define_pitch 'C9', 120
|
160
|
+
define_pitch 'Db9', 121
|
161
|
+
define_pitch 'D9', 122
|
162
|
+
define_pitch 'Eb9', 123
|
163
|
+
define_pitch 'E9', 124
|
164
|
+
define_pitch 'F9', 125
|
165
|
+
define_pitch 'Gb9', 126
|
166
|
+
define_pitch 'G9', 127
|
32
167
|
|
33
168
|
PITCHES.freeze
|
34
169
|
PITCH_NAMES.freeze
|
35
170
|
|
36
|
-
# Lookup the value of an pitch constant by name.
|
37
|
-
# @example lookup value of 'C3'
|
38
|
-
# MTK::Core::Pitches['C3']
|
39
|
-
# @see Core::Pitch.from_s
|
40
|
-
# @note Unlike {Core::Pitch.from_s} this method will accept either '_' (underscore) or '-' (minus) and treat it like '-' (minus)
|
41
|
-
# @note Unlike {Core::Pitch.from_s} this method only accepts the accidental 'b'
|
42
|
-
def self.[](name)
|
43
|
-
begin
|
44
|
-
const_get name.sub('-','_')
|
45
|
-
rescue
|
46
|
-
nil
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
171
|
end
|
51
172
|
end
|
52
173
|
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
=begin
|
178
|
+
|
179
|
+
Script for generating the define_pitch statements in this file.
|
180
|
+
Run in the irb console after requiring 'mtk'
|
181
|
+
|
182
|
+
128.times do |value|
|
183
|
+
pitch = MTK::Core::Pitch.from_i(value)
|
184
|
+
octave_str = pitch.octave.to_s.sub(/-/,'_') # '_1' for -1
|
185
|
+
name = "#{pitch.pitch_class}#{octave_str}"
|
186
|
+
puts " define_pitch '#{name}', #{value}"
|
187
|
+
end
|
188
|
+
|
189
|
+
=end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'mtk/io/midi_output'
|
4
|
+
require 'mtk/lang/tutorial_lesson'
|
5
|
+
|
6
|
+
module MTK
|
7
|
+
module Lang
|
8
|
+
|
9
|
+
# @private
|
10
|
+
class Tutorial
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@current_lesson_index = 0
|
14
|
+
|
15
|
+
@lessons = [
|
16
|
+
###################### 79 character width for description #####################
|
17
|
+
{
|
18
|
+
title: 'Pitch Classes (diatonic)',
|
19
|
+
description: "
|
20
|
+
The diatonic pitch classes are the 7 white keys on a piano in a given octave.
|
21
|
+
They can be used to play, for example, the C major or A natural minor scales.
|
22
|
+
|
23
|
+
To play a diatonic pitch class, enter #{'one'.bold.underline} of the following letters
|
24
|
+
(must be upper-case):
|
25
|
+
|
26
|
+
C D E F G A B
|
27
|
+
",
|
28
|
+
validation: :diatonic_pitch_class,
|
29
|
+
},
|
30
|
+
{
|
31
|
+
title: 'Pitch Classes (chromatic)',
|
32
|
+
description: "
|
33
|
+
The chromatic pitch classes are the 12 white or black keys on a piano in a
|
34
|
+
given octave. They form the basis of all 'western' music theory.
|
35
|
+
|
36
|
+
To play a chromatic pitch class, enter any of the 7 diatonic pitch classes
|
37
|
+
immediately followed by 0, 1, or 2 flats (b) or sharps (#). Each flat (b)
|
38
|
+
lowers the pitch by a half step and each sharp (#) raises by a half step.
|
39
|
+
|
40
|
+
Here are some examples, try entering #{'one'.bold.underline} of the following:
|
41
|
+
|
42
|
+
C# Bb A## Ebb F
|
43
|
+
",
|
44
|
+
validation: :pitch_class,
|
45
|
+
},
|
46
|
+
{
|
47
|
+
title: 'Pitches',
|
48
|
+
description: "
|
49
|
+
To play a pitch, enter a (chromatic) pitch class immediately following by an
|
50
|
+
octave number.
|
51
|
+
|
52
|
+
There is no universal standard numbering scheme for octaves in music.
|
53
|
+
This library uses \"scientific pitch notation\", which defines
|
54
|
+
C4 as \"Middle C\" and A4 as the standard tuning pitch A440.
|
55
|
+
|
56
|
+
In this library, C-1 is the lowest note available and G9 is the highest,
|
57
|
+
corresponding to MIDI pitch values 0 and 127. If you try to play a pitch
|
58
|
+
outside this range, it will be mapped to the closest available pitch.
|
59
|
+
|
60
|
+
Here are some examples, try entering #{'one'.bold.underline} of the following:
|
61
|
+
|
62
|
+
G3 Eb4 F#5 B-1 C##9 Dbb6
|
63
|
+
",
|
64
|
+
validation: :pitch,
|
65
|
+
},
|
66
|
+
{
|
67
|
+
title: 'Sequences',
|
68
|
+
description: "
|
69
|
+
To play a sequence of pitches or pitch classes, enter them with spaces in
|
70
|
+
between. Pitches and pitch classes may be interchanged in a given sequence.
|
71
|
+
Any pitch class will output a pitch closest to the previous pitch (starting
|
72
|
+
from C4 at the beginning of the sequence).
|
73
|
+
|
74
|
+
Here is an example (Note, unlike previous lessons, enter the #{'entire line'.bold.underline}):
|
75
|
+
|
76
|
+
C5 C G5 G A A G
|
77
|
+
",
|
78
|
+
validation: :bare_sequence,
|
79
|
+
},
|
80
|
+
{
|
81
|
+
title: 'Repetition',
|
82
|
+
description: "
|
83
|
+
To repeat a note, suffix a pitch or pitch class with *N, where N is the
|
84
|
+
number of repetitions. You can also wrap a subsequence of notes with
|
85
|
+
parentheses and repeat them. Here is an example sequence with repetition:
|
86
|
+
|
87
|
+
C*3 D (E D)*2 C
|
88
|
+
|
89
|
+
You can also nest repetitions (optional whitespace added for readability):
|
90
|
+
|
91
|
+
( C5 (E G)*2 )*2
|
92
|
+
",
|
93
|
+
validation: /\*/,
|
94
|
+
},
|
95
|
+
|
96
|
+
].map{|lesson_options| TutorialLesson.new(lesson_options) }
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def run(output)
|
101
|
+
puts SEPARATOR
|
102
|
+
puts
|
103
|
+
puts "Welcome to the MTK syntax tutorial!".bold.yellow
|
104
|
+
puts
|
105
|
+
puts "MTK is the Music Tool Kit for Ruby, which includes a custom syntax for"
|
106
|
+
puts "generating musical patterns. This tutorial has a variety of lessons to teach"
|
107
|
+
puts "you the syntax. It assumes basic familiarity with music theory."
|
108
|
+
puts
|
109
|
+
puts "Make sure your speakers are on and the volume is turned up."
|
110
|
+
puts
|
111
|
+
puts "This is a work in progress. Check back in future versions for more lessons."
|
112
|
+
puts
|
113
|
+
puts "#{'NOTE:'.bold} MTK syntax is case-sensitive. Upper vs lower-case matters."
|
114
|
+
puts
|
115
|
+
|
116
|
+
output = ensure_output(output)
|
117
|
+
loop{ select_lesson.run(output) }
|
118
|
+
|
119
|
+
rescue SystemExit, Interrupt
|
120
|
+
puts
|
121
|
+
puts
|
122
|
+
puts "Goodbye!"
|
123
|
+
puts
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# table of contents
|
128
|
+
def toc
|
129
|
+
@lessons.map.with_index do |lesson,index|
|
130
|
+
"#{'> '.bold.yellow if @current_lesson_index == index}#{index+1}: #{lesson}"
|
131
|
+
end.join("\n")
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def select_lesson
|
136
|
+
puts
|
137
|
+
puts SEPARATOR
|
138
|
+
puts
|
139
|
+
puts "Lessons".bold.yellow
|
140
|
+
puts
|
141
|
+
|
142
|
+
all_done = @current_lesson_index >= @lessons.length
|
143
|
+
lesson = nil
|
144
|
+
while lesson == nil
|
145
|
+
puts toc
|
146
|
+
puts
|
147
|
+
if all_done
|
148
|
+
puts "You've completed the last lesson!"
|
149
|
+
puts "To explore more, try running #{$0} with the --eval option."
|
150
|
+
puts
|
151
|
+
end
|
152
|
+
puts "Press Ctrl+C to exit at any time.".bold
|
153
|
+
puts
|
154
|
+
print "Select a lesson number, or press enter to ".blue
|
155
|
+
if all_done
|
156
|
+
puts "exit:".blue
|
157
|
+
else
|
158
|
+
puts "go to the next one ".blue + "(indicated by " + '>'.bold.yellow + "):"
|
159
|
+
end
|
160
|
+
|
161
|
+
input = gets.strip
|
162
|
+
lesson_index = case input
|
163
|
+
when /^\d+$/
|
164
|
+
input.to_i - 1
|
165
|
+
when ''
|
166
|
+
if all_done then raise SystemExit.new else @current_lesson_index end
|
167
|
+
else
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
lesson = @lessons[lesson_index] if lesson_index and lesson_index >= 0
|
172
|
+
|
173
|
+
puts "Invalid lesson number: #{input}\n\n" if lesson.nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
@current_lesson_index = lesson_index + 1
|
177
|
+
lesson
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
#####################################################
|
182
|
+
private
|
183
|
+
|
184
|
+
def ensure_output(output)
|
185
|
+
unless output
|
186
|
+
puts SEPARATOR
|
187
|
+
puts
|
188
|
+
puts "Select an output".bold.yellow
|
189
|
+
puts
|
190
|
+
puts "You need to select a MIDI output device before you can hear sound."
|
191
|
+
puts
|
192
|
+
|
193
|
+
require 'rbconfig'
|
194
|
+
case RbConfig::CONFIG['host_os'].downcase
|
195
|
+
when /darwin/
|
196
|
+
puts "You appear to be on OS X."
|
197
|
+
puts "You can use the \"Apple DLS Synthesizer\" to hear sound with no extra setup."
|
198
|
+
puts "It's recommended you use this unless you have a good reason to do otherwise."
|
199
|
+
when /win/
|
200
|
+
puts "You appear to be on Windows"
|
201
|
+
puts "You can use the \"Microsoft Synthesizer\" to hear sound with no extra setup."
|
202
|
+
puts "It's recommended you use this unless you have a good reason to do otherwise."
|
203
|
+
end
|
204
|
+
|
205
|
+
until output
|
206
|
+
puts
|
207
|
+
puts "Available MIDI outputs:".bold.yellow
|
208
|
+
MTK::IO::MIDIOutput.devices.each.with_index do |device,index|
|
209
|
+
name = device.name
|
210
|
+
name += " (#{device.id})" if device.respond_to? :id
|
211
|
+
puts "#{index+1}: #{name}"
|
212
|
+
end
|
213
|
+
|
214
|
+
puts "Select an output number:".blue
|
215
|
+
|
216
|
+
input = STDIN.gets.strip
|
217
|
+
if input =~ /^\d+$/
|
218
|
+
number = input.to_i
|
219
|
+
if number > 0
|
220
|
+
device = MTK::IO::MIDIOutput.devices[number-1]
|
221
|
+
output = MTK::IO::MIDIOutput.open(device) if device
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
puts "Invalid selection.".red unless device
|
226
|
+
end
|
227
|
+
|
228
|
+
puts
|
229
|
+
puts "OK! Using output '#{output.name}'".bold.green
|
230
|
+
puts "#{'NOTE:'.bold} You can skip the output selection step in the future by running "
|
231
|
+
puts "#{$0} with the --output option."
|
232
|
+
puts "Press enter to continue".blue
|
233
|
+
gets
|
234
|
+
end
|
235
|
+
output
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
####################################################################################################
|
244
|
+
# Patch String to support terminal colors
|
245
|
+
|
246
|
+
# @private
|
247
|
+
class String
|
248
|
+
{
|
249
|
+
bold: 1,
|
250
|
+
underline: 4,
|
251
|
+
red: 31,
|
252
|
+
green: 32,
|
253
|
+
yellow: 33,
|
254
|
+
blue: 36 # really this cyan but the standard blue is too dark IMO
|
255
|
+
}.each do |effect,code|
|
256
|
+
if $tutorial_color
|
257
|
+
define_method effect do
|
258
|
+
"\e[#{code}m#{self}\e[0m"
|
259
|
+
end
|
260
|
+
else
|
261
|
+
define_method effect do
|
262
|
+
self
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
####################################################################################################
|
271
|
+
# And now that we've got some colors we can define colored constants
|
272
|
+
module MTK
|
273
|
+
module Lang
|
274
|
+
# @private
|
275
|
+
class Tutorial
|
276
|
+
SEPARATOR = "===============================================================================".bold.yellow
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|