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
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
|
+
|
data/examples/notation.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# NOTE: experimental example!
|
2
|
+
# This requires Lilypond to be installed, see http://lilypond.org/
|
3
|
+
# The lilypond command must be on your PATH or specificed via the LILYPOND_PATH environment variable.
|
4
|
+
|
1
5
|
require 'mtk'
|
2
6
|
require 'mtk/io/notation'
|
3
7
|
include MTK
|
@@ -13,7 +17,7 @@ arg_error "MTK syntax string not provided" unless syntax
|
|
13
17
|
|
14
18
|
|
15
19
|
file = ARGV[1]
|
16
|
-
arg_error "The output_file must end in '.png', '.pdf', or '.ps'" unless file
|
20
|
+
arg_error "The output_file must end in '.png', '.pdf', or '.ps'" unless file =~ /\.(png|pdf|ps)$/
|
17
21
|
|
18
22
|
|
19
23
|
sequencer = MTK::Lang::Parser.parse(syntax)
|
data/examples/tone_row_melody.rb
CHANGED
@@ -12,7 +12,7 @@ file = ARGV[0] || 'MTK-tone_row_melody.mid'
|
|
12
12
|
|
13
13
|
row = PitchClassSet Db, G, Ab, F, Eb, E, D, C, B, Gb, A, Bb
|
14
14
|
pitch_pattern = Patterns.Cycle *row
|
15
|
-
rhythm_pattern = Patterns.Choice s,
|
15
|
+
rhythm_pattern = Patterns.Choice s, e, e+s, q # choose between sixteenth, eighth, dotted eighth, and quarter
|
16
16
|
|
17
17
|
chain = Patterns.Chain pitch_pattern, rhythm_pattern, min_elements: 36, max_elements: 36
|
18
18
|
|
data/lib/mtk.rb
CHANGED
data/lib/mtk/core/duration.rb
CHANGED
@@ -3,18 +3,20 @@ module MTK
|
|
3
3
|
|
4
4
|
# A measure of time in musical beats.
|
5
5
|
# May be negative to indicate a rest, which uses the absolute value for the effective duration.
|
6
|
+
#
|
7
|
+
# @see Lang::Durations
|
6
8
|
class Duration
|
7
9
|
|
8
10
|
include Comparable
|
9
11
|
|
10
12
|
# The names of the base durations. See {MTK::Lang::Durations} for more info.
|
11
|
-
NAMES = %w[w h q
|
13
|
+
NAMES = %w[w h q e s r x].freeze
|
12
14
|
|
13
15
|
VALUES_BY_NAME = {
|
14
16
|
'w' => 4,
|
15
17
|
'h' => 2,
|
16
18
|
'q' => 1,
|
17
|
-
'
|
19
|
+
'e' => Rational(1,2),
|
18
20
|
's' => Rational(1,4),
|
19
21
|
'r' => Rational(1,8),
|
20
22
|
'x' => Rational(1,16)
|
@@ -55,7 +57,7 @@ module MTK
|
|
55
57
|
# @example lookup the value of 3/4w, which three-quarters of a whole note (3 beats):
|
56
58
|
# MTK::Core::Duration.from_s('3/4w')
|
57
59
|
def self.from_s(s)
|
58
|
-
if s =~ /^(-)?(\d+([\.\/]\d+)?)?([
|
60
|
+
if s =~ /^(-)?(\d+([\.\/]\d+)?)?([whqesrx])((\.|t)*)$/i
|
59
61
|
name = $4.downcase
|
60
62
|
modifier = $5.downcase
|
61
63
|
modifier << $1 if $1 # negation
|
@@ -95,6 +97,7 @@ module MTK
|
|
95
97
|
# The magnitude (absolute value) of the duration.
|
96
98
|
# This is the actual duration for rests.
|
97
99
|
# @see #rest?
|
100
|
+
# @see #abs
|
98
101
|
def length
|
99
102
|
@value < 0 ? -@value : @value
|
100
103
|
end
|
@@ -106,6 +109,18 @@ module MTK
|
|
106
109
|
@value < 0
|
107
110
|
end
|
108
111
|
|
112
|
+
# Force resets to be non-rests, otherwise don't change the duration.
|
113
|
+
# @see #-@
|
114
|
+
# @see #length
|
115
|
+
# @see #rest?
|
116
|
+
def abs
|
117
|
+
if @value < 0
|
118
|
+
-self
|
119
|
+
else
|
120
|
+
self
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
109
124
|
# The number of beats as a floating point number
|
110
125
|
def to_f
|
111
126
|
@value.to_f
|
data/lib/mtk/core/intensity.rb
CHANGED
@@ -2,12 +2,14 @@ module MTK
|
|
2
2
|
module Core
|
3
3
|
|
4
4
|
# A measure of intensity, using an underlying value in the range 0.0-1.0
|
5
|
+
#
|
6
|
+
# @see Lang::Intensities
|
5
7
|
class Intensity
|
6
8
|
|
7
9
|
include Comparable
|
8
10
|
|
9
|
-
# The names of the base intensities. See {
|
10
|
-
NAMES = %w[ppp pp p mp mf
|
11
|
+
# The names of the base intensities. See {MTK::Lang::Intensities} for more info.
|
12
|
+
NAMES = %w[ppp pp p mp mf f ff fff].freeze
|
11
13
|
|
12
14
|
VALUES_BY_NAME = {
|
13
15
|
'ppp' => 0.125,
|
@@ -15,7 +17,7 @@ module MTK
|
|
15
17
|
'p' => 0.375,
|
16
18
|
'mp' => 0.5,
|
17
19
|
'mf' => 0.625,
|
18
|
-
'
|
20
|
+
'f' => 0.75,
|
19
21
|
'ff' => 0.875,
|
20
22
|
'fff' => 1.0
|
21
23
|
}
|
data/lib/mtk/core/interval.rb
CHANGED
@@ -2,29 +2,35 @@ module MTK
|
|
2
2
|
module Core
|
3
3
|
|
4
4
|
# A measure of intensity, using an underlying value in the range 0.0-1.0
|
5
|
+
#
|
6
|
+
# @see Lang::Intervals
|
5
7
|
class Interval
|
6
8
|
|
7
9
|
include Comparable
|
8
10
|
|
9
11
|
# The preferred names of all pre-defined intervals
|
12
|
+
# @see ALL_NAMES
|
10
13
|
NAMES = %w[P1 m2 M2 m3 M3 P4 TT P5 m6 M6 m7 M7 P8].freeze
|
11
14
|
|
12
15
|
# All valid names of pre-defined intervals, indexed by their value.
|
16
|
+
# @see ALL_NAMES
|
17
|
+
# @see NAMES
|
18
|
+
# @see http://en.wikipedia.org/wiki/Interval_(music)#Main_intervals
|
13
19
|
NAMES_BY_VALUE =
|
14
|
-
[ # names # value # description
|
15
|
-
%w( P1
|
16
|
-
%w( m2
|
17
|
-
%w( M2
|
18
|
-
%w( m3
|
19
|
-
%w( M3
|
20
|
-
%w( P4
|
21
|
-
%w( TT
|
22
|
-
%w( P5
|
23
|
-
%w( m6
|
24
|
-
%w( M6
|
25
|
-
%w( m7
|
26
|
-
%w( M7
|
27
|
-
%w( P8
|
20
|
+
[ # names # value # description # enharmonic equivalents
|
21
|
+
%w( P1 d2 ), # 0 # unison # diminished second
|
22
|
+
%w( m2 a1 ), # 1 # minor second # augmented unison
|
23
|
+
%w( M2 d3 ), # 2 # major second # diminished third
|
24
|
+
%w( m3 a2 ), # 3 # minor third # augmented second
|
25
|
+
%w( M3 d4 ), # 4 # major third # diminished fourth
|
26
|
+
%w( P4 a3 ), # 5 # perfect fourth # augmented third
|
27
|
+
%w( TT a4 d5 ),# 6 # tritone # augmented fourth, diminished fifth
|
28
|
+
%w( P5 d6 ), # 7 # perfect fifth # diminished sixth
|
29
|
+
%w( m6 a5 ), # 8 # minor sixth # augmented fifth
|
30
|
+
%w( M6 d7 ), # 9 # major sixth # diminished seventh
|
31
|
+
%w( m7 a6 ), # 10 # minor seventh # augmented sixth
|
32
|
+
%w( M7 d8 ), # 11 # major seventh # diminished octave
|
33
|
+
%w( P8 a7 ) # 12 # octave # augmented seventh
|
28
34
|
].freeze
|
29
35
|
|
30
36
|
# A mapping from intervals names to their value
|
@@ -35,6 +41,7 @@ module MTK
|
|
35
41
|
].freeze
|
36
42
|
|
37
43
|
# All valid interval names
|
44
|
+
# @see NAMES_BY_VALUE
|
38
45
|
ALL_NAMES = NAMES_BY_VALUE.flatten.freeze
|
39
46
|
|
40
47
|
|