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