mtk 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -2
- data/DEVELOPMENT_NOTES.md +114 -0
- data/INTRO.md +64 -8
- data/LICENSE.txt +1 -1
- data/README.md +31 -102
- data/Rakefile +56 -18
- data/bin/mtk +215 -0
- data/examples/crescendo.rb +5 -5
- data/examples/drum_pattern1.rb +23 -0
- data/examples/dynamic_pattern.rb +8 -11
- data/examples/gets_and_play.rb +26 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +8 -10
- data/examples/random_tone_row.rb +2 -2
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +8 -0
- data/examples/tone_row_melody.rb +6 -6
- data/lib/mtk.rb +52 -40
- data/lib/mtk/chord.rb +55 -0
- data/lib/mtk/constants/durations.rb +57 -0
- data/lib/mtk/constants/intensities.rb +61 -0
- data/lib/mtk/constants/intervals.rb +73 -0
- data/lib/mtk/constants/pitch_classes.rb +29 -0
- data/lib/mtk/constants/pitches.rb +52 -0
- data/lib/mtk/duration.rb +211 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/helpers/collection.rb +164 -0
- data/lib/mtk/helpers/convert.rb +36 -0
- data/lib/mtk/helpers/lilypond.rb +162 -0
- data/lib/mtk/helpers/output_selector.rb +67 -0
- data/lib/mtk/helpers/pitch_collection.rb +23 -0
- data/lib/mtk/helpers/pseudo_constants.rb +26 -0
- data/lib/mtk/intensity.rb +156 -0
- data/lib/mtk/interval.rb +155 -0
- data/lib/mtk/lang/mtk_grammar.citrus +190 -13
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/melody.rb +94 -0
- data/lib/mtk/midi/dls_synth_device.rb +144 -0
- data/lib/mtk/midi/dls_synth_output.rb +62 -0
- data/lib/mtk/midi/file.rb +67 -32
- data/lib/mtk/midi/input.rb +97 -0
- data/lib/mtk/midi/jsound_input.rb +36 -17
- data/lib/mtk/midi/jsound_output.rb +48 -46
- data/lib/mtk/midi/output.rb +195 -0
- data/lib/mtk/midi/unimidi_input.rb +117 -0
- data/lib/mtk/midi/unimidi_output.rb +121 -0
- data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
- data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/pitch.rb +7 -6
- data/lib/mtk/pitch_class.rb +124 -46
- data/lib/mtk/pitch_class_set.rb +58 -35
- data/lib/mtk/sequencers/event_builder.rb +131 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
- data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
- data/lib/mtk/timeline.rb +39 -22
- data/lib/mtk/variable.rb +32 -0
- data/spec/mtk/chord_spec.rb +83 -0
- data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
- data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
- data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
- data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
- data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
- data/spec/mtk/duration_spec.rb +372 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
- data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/intensity_spec.rb +289 -0
- data/spec/mtk/interval_spec.rb +265 -0
- data/spec/mtk/lang/parser_spec.rb +597 -0
- data/spec/mtk/melody_spec.rb +223 -0
- data/spec/mtk/midi/file_spec.rb +16 -16
- data/spec/mtk/midi/jsound_input_spec.rb +11 -0
- data/spec/mtk/midi/jsound_output_spec.rb +11 -0
- data/spec/mtk/midi/output_spec.rb +102 -0
- data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
- data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
- data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
- data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
- data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
- data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/pitch_class_set_spec.rb +23 -21
- data/spec/mtk/pitch_class_spec.rb +151 -39
- data/spec/mtk/pitch_spec.rb +22 -1
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
- data/spec/mtk/timeline_spec.rb +109 -16
- data/spec/mtk/variable_spec.rb +52 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +188 -91
- data/lib/mtk/_constants/durations.rb +0 -80
- data/lib/mtk/_constants/intensities.rb +0 -81
- data/lib/mtk/_constants/intervals.rb +0 -85
- data/lib/mtk/_constants/pitch_classes.rb +0 -35
- data/lib/mtk/_constants/pitches.rb +0 -49
- data/lib/mtk/event.rb +0 -70
- data/lib/mtk/helper/collection.rb +0 -114
- data/lib/mtk/helper/event_builder.rb +0 -85
- data/lib/mtk/helper/pseudo_constants.rb +0 -26
- data/lib/mtk/lang/grammar.rb +0 -17
- data/lib/mtk/note.rb +0 -63
- data/lib/mtk/pattern/abstract_pattern.rb +0 -132
- data/lib/mtk/pattern/cycle.rb +0 -51
- data/lib/mtk/pattern/enumerator.rb +0 -26
- data/lib/mtk/pattern/function.rb +0 -46
- data/lib/mtk/pattern/sequence.rb +0 -30
- data/lib/mtk/pitch_set.rb +0 -84
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
- data/lib/mtk/transform/invertible.rb +0 -15
- data/lib/mtk/transform/mappable.rb +0 -18
- data/lib/mtk/transform/set_theory_operations.rb +0 -34
- data/lib/mtk/transform/transposable.rb +0 -14
- data/spec/mtk/event_spec.rb +0 -139
- data/spec/mtk/helper/event_builder_spec.rb +0 -92
- data/spec/mtk/lang/grammar_spec.rb +0 -100
- data/spec/mtk/note_spec.rb +0 -115
- data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
- data/spec/mtk/pattern/sequence_spec.rb +0 -151
- data/spec/mtk/pitch_set_spec.rb +0 -198
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,164 @@
|
|
1
|
+
module MTK
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# Given a method #elements, which returns an Array of elements in the collection,
|
5
|
+
# including this module will make the class Enumerable and provide various methods you'd expect from an Array.
|
6
|
+
module Collection
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# A mutable array of elements in this collection
|
10
|
+
def to_a
|
11
|
+
Array.new(elements) # we construct a new array since some including classes make elements be immutable
|
12
|
+
end
|
13
|
+
|
14
|
+
# The number of elements in this collection
|
15
|
+
def size
|
16
|
+
elements.size
|
17
|
+
end
|
18
|
+
alias length size
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
elements.nil? or elements.size == 0
|
22
|
+
end
|
23
|
+
|
24
|
+
# The each iterator for providing Enumerable functionality
|
25
|
+
def each &block
|
26
|
+
elements.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# the original Enumerable#map implementation, which returns an Array
|
30
|
+
alias enumerable_map map
|
31
|
+
|
32
|
+
# the overriden #map implementation, which returns an object of the same type
|
33
|
+
def map &block
|
34
|
+
clone_with enumerable_map(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# The first element
|
38
|
+
def first(n=nil)
|
39
|
+
n ? elements.first(n) : elements.first
|
40
|
+
end
|
41
|
+
|
42
|
+
# The last element
|
43
|
+
def last(n=nil)
|
44
|
+
n ? elements.last(n) : elements.last
|
45
|
+
end
|
46
|
+
|
47
|
+
# The element with the given index
|
48
|
+
def [](index)
|
49
|
+
elements[index]
|
50
|
+
end
|
51
|
+
|
52
|
+
def repeat(times=2)
|
53
|
+
full_repetitions, fractional_repetitions = times.floor, times%1 # split into int and fractional part
|
54
|
+
repeated = elements * full_repetitions
|
55
|
+
repeated += elements[0...elements.size*fractional_repetitions]
|
56
|
+
clone_with repeated
|
57
|
+
end
|
58
|
+
|
59
|
+
def permute
|
60
|
+
clone_with elements.shuffle
|
61
|
+
end
|
62
|
+
alias shuffle permute
|
63
|
+
|
64
|
+
def rotate(offset=1)
|
65
|
+
clone_with elements.rotate(offset)
|
66
|
+
end
|
67
|
+
|
68
|
+
def concat(other)
|
69
|
+
other_elements = (other.respond_to? :elements) ? other.elements : other
|
70
|
+
clone_with(elements + other_elements)
|
71
|
+
end
|
72
|
+
|
73
|
+
def reverse
|
74
|
+
clone_with elements.reverse
|
75
|
+
end
|
76
|
+
alias retrograde reverse
|
77
|
+
|
78
|
+
|
79
|
+
# Partition the collection into an Array of sub-collections.
|
80
|
+
#
|
81
|
+
# With a Numeric argument: partition the elements into collections of the given size (plus whatever's left over).
|
82
|
+
#
|
83
|
+
# With an Array argument: partition the elements into collections of the given sizes.
|
84
|
+
#
|
85
|
+
# Otherwise if a block is given: partition the elements into collections with the same block return value.
|
86
|
+
#
|
87
|
+
def partition(arg=nil, &block)
|
88
|
+
partitions = nil
|
89
|
+
case arg
|
90
|
+
when Numeric
|
91
|
+
partitions = self.each_slice(arg)
|
92
|
+
|
93
|
+
when Enumerable
|
94
|
+
partitions = []
|
95
|
+
items, sizes = self.to_enum, arg.to_enum
|
96
|
+
group = []
|
97
|
+
size = sizes.next
|
98
|
+
loop do
|
99
|
+
item = items.next
|
100
|
+
if group.size < size
|
101
|
+
group << item
|
102
|
+
else
|
103
|
+
partitions << group
|
104
|
+
group = []
|
105
|
+
size = sizes.next
|
106
|
+
group << item
|
107
|
+
end
|
108
|
+
end
|
109
|
+
partitions << group unless group.empty?
|
110
|
+
|
111
|
+
else
|
112
|
+
if block
|
113
|
+
group = Hash.new{|h,k| h[k] = [] }
|
114
|
+
if block.arity == 2
|
115
|
+
self.each_with_index{|item,index| group[block[item,index]] << item }
|
116
|
+
else
|
117
|
+
self.each{|item| group[block[item]] << item }
|
118
|
+
end
|
119
|
+
partitions = group.values
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if partitions
|
124
|
+
partitions.map{|p| self.class.from_a(p) }
|
125
|
+
else
|
126
|
+
self
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def ==(other)
|
131
|
+
if other.respond_to? :elements
|
132
|
+
if other.respond_to? :options
|
133
|
+
elements == other.elements and @options == other.options
|
134
|
+
else
|
135
|
+
elements == other.elements
|
136
|
+
end
|
137
|
+
else
|
138
|
+
elements == other
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Create a copy of the collection.
|
143
|
+
# In order to use this method, the including class must implement .from_a()
|
144
|
+
def clone
|
145
|
+
clone_with to_a
|
146
|
+
end
|
147
|
+
|
148
|
+
#################################
|
149
|
+
private
|
150
|
+
|
151
|
+
# "clones" the object with the given elements, attempting to maintain any @options
|
152
|
+
# This is designed to work with 2 argument constructors: def initialize(elements, options=default)
|
153
|
+
def clone_with elements
|
154
|
+
from_a = self.class.method(:from_a)
|
155
|
+
if @options and from_a.arity == -2
|
156
|
+
from_a[elements, @options]
|
157
|
+
else
|
158
|
+
from_a[elements]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MTK
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module Convert
|
5
|
+
|
6
|
+
def to_pitch_classes(*anything)
|
7
|
+
anything = anything.first if anything.length == 1
|
8
|
+
if anything.respond_to? :to_pitch_classes
|
9
|
+
anything.to_pitch_classes
|
10
|
+
else
|
11
|
+
case anything
|
12
|
+
when Enumerable then anything.map{|item| PitchClass(item) }
|
13
|
+
else [PitchClass(anything)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
module_function :to_pitch_classes
|
18
|
+
|
19
|
+
|
20
|
+
def to_pitches(*anything)
|
21
|
+
anything = anything.first if anything.length == 1
|
22
|
+
if anything.respond_to? :to_pitches
|
23
|
+
anything.to_pitches
|
24
|
+
else
|
25
|
+
case anything
|
26
|
+
when Enumerable then anything.map{|item| Pitch(item) }
|
27
|
+
else [Pitch(anything)]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module_function :to_pitches
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module MTK
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
# Uses {Timeline}s to generates music notation graphics with {http://lilypond.org/ Lilypond}.
|
9
|
+
# @note This class is optional and only available if you require 'mtk/helpers/lilypond'.
|
10
|
+
# @note To make notation graphics, {http://lilypond.org/download.html Lilypond} must be installed
|
11
|
+
# and you must follow the "Running on the command-line" instructions (found on the download page for
|
12
|
+
# your operating system). If the lilypond command is not on your PATH, set the environment variable LILYPOND_PATH
|
13
|
+
class Lilypond
|
14
|
+
|
15
|
+
LILYPOND_PATH = ENV['LILYPOND_PATH'] || 'lilypond'
|
16
|
+
|
17
|
+
VALID_FORMATS = %w( png pdf ps )
|
18
|
+
|
19
|
+
|
20
|
+
def initialize(file, options={})
|
21
|
+
@file = file
|
22
|
+
@options = options
|
23
|
+
|
24
|
+
@format = File.extname(file)[1..-1].downcase
|
25
|
+
raise ArgumentError.new("Invalid file format '#{@format}'") unless VALID_FORMATS.include? @format
|
26
|
+
|
27
|
+
@dpi = options[:dpi]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.open(file, options={})
|
31
|
+
new(file,options)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def write(timeline)
|
36
|
+
lilypond_syntax = syntax_for_timeline(timeline)
|
37
|
+
puts lilypond_syntax
|
38
|
+
puts "_____________________________"
|
39
|
+
Tempfile.open('mtk_lilypond') do |lilypond_file|
|
40
|
+
Dir.mktmpdir do |tmpdir|
|
41
|
+
# use the directory...
|
42
|
+
#open("#{dir}/foo", "w") { ... }
|
43
|
+
|
44
|
+
lilypond_file.write(lilypond_syntax)
|
45
|
+
lilypond_file.flush
|
46
|
+
|
47
|
+
cmd = ['lilypond', '-dbackend=eps', "-f#{@format}", "--output=\"#{tmpdir}\""]
|
48
|
+
cmd << "-dresolution=#{@dpi}" if @dpi
|
49
|
+
cmd << lilypond_file.path
|
50
|
+
cmd = cmd.join(' ')
|
51
|
+
|
52
|
+
puts cmd if $DEBUG
|
53
|
+
lilypond_command_output = `#{cmd}`
|
54
|
+
puts lilypond_command_output if $DEBUG
|
55
|
+
|
56
|
+
output_file = Dir["#{tmpdir}/*.#{@format}"].first
|
57
|
+
|
58
|
+
FileUtils.cp output_file, @file
|
59
|
+
|
60
|
+
puts "Wrote #{@file}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
########################
|
67
|
+
private
|
68
|
+
|
69
|
+
QUANTIZATION_INTERVAL = 0.0625 # 64th note granularity
|
70
|
+
|
71
|
+
SYNTAX_PREFIX = '
|
72
|
+
\language "english"
|
73
|
+
\paper{
|
74
|
+
oddFooterMarkup=##f
|
75
|
+
oddHeaderMarkup=##f
|
76
|
+
}
|
77
|
+
\new PianoStaff {
|
78
|
+
\autochange {
|
79
|
+
'
|
80
|
+
|
81
|
+
SYNTAX_SUFFIX = '
|
82
|
+
}
|
83
|
+
}
|
84
|
+
'
|
85
|
+
|
86
|
+
def syntax_for_timeline(timeline)
|
87
|
+
quantized_timeline = timeline.flatten.quantize(QUANTIZATION_INTERVAL)
|
88
|
+
last_time = 0
|
89
|
+
last_duration = 0
|
90
|
+
|
91
|
+
s = ''
|
92
|
+
s << SYNTAX_PREFIX
|
93
|
+
|
94
|
+
for time,events in quantized_timeline
|
95
|
+
|
96
|
+
# handle rests between notes
|
97
|
+
delta = time - last_time
|
98
|
+
if delta > last_duration
|
99
|
+
s << 'r'+syntax_for_duration(delta - last_duration)
|
100
|
+
s << ' '
|
101
|
+
end
|
102
|
+
|
103
|
+
notes = events.find_all{|event| event.type == :note }
|
104
|
+
|
105
|
+
if notes.length > 1
|
106
|
+
# a chord
|
107
|
+
s << '<'
|
108
|
+
total_duration = 0
|
109
|
+
for note in notes
|
110
|
+
total_duration += note.duration
|
111
|
+
s << syntax_for_pitch(note.pitch)
|
112
|
+
s << ' '
|
113
|
+
end
|
114
|
+
s << '>'
|
115
|
+
duration = total_duration.to_f/notes.length
|
116
|
+
s << syntax_for_duration(duration)
|
117
|
+
|
118
|
+
else # a single note
|
119
|
+
note = notes.first
|
120
|
+
s << syntax_for_pitch(note.pitch)
|
121
|
+
duration = note.duration
|
122
|
+
s << syntax_for_duration(duration)
|
123
|
+
end
|
124
|
+
|
125
|
+
last_time = time
|
126
|
+
last_duration = duration
|
127
|
+
s << ' '
|
128
|
+
end
|
129
|
+
|
130
|
+
s << SYNTAX_SUFFIX
|
131
|
+
s
|
132
|
+
end
|
133
|
+
|
134
|
+
def syntax_for_pitch(pitch)
|
135
|
+
syntax = pitch.pitch_class.name.downcase
|
136
|
+
if syntax.length > 0
|
137
|
+
syntax = syntax[0] + syntax[1..-1].gsub('b','f') # .gsub('#','s') pitch class names never have '#'
|
138
|
+
end
|
139
|
+
oct = pitch.octave
|
140
|
+
while oct > 3
|
141
|
+
syntax << "'"
|
142
|
+
oct -= 1
|
143
|
+
end
|
144
|
+
while oct < 3
|
145
|
+
syntax << ","
|
146
|
+
oct += 1
|
147
|
+
end
|
148
|
+
syntax
|
149
|
+
end
|
150
|
+
|
151
|
+
def syntax_for_duration(duration)
|
152
|
+
# TODO: handle dots, triplets, and ties of arbitrary durations
|
153
|
+
duration = MTK::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
|
154
|
+
syntax = (4.0/duration).round
|
155
|
+
syntax = 1 if syntax < 1
|
156
|
+
syntax.to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'mtk/midi/output'
|
2
|
+
|
3
|
+
module MTK
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
# Optional class for loading the preferred platform-specific implementation of an output,
|
7
|
+
# and methods to assist with selecting an output.
|
8
|
+
class OutputSelector
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def output
|
13
|
+
MTK::MIDI::Output
|
14
|
+
end
|
15
|
+
|
16
|
+
# Look for an output by name using case insensitive matching,
|
17
|
+
# treating underscore like either an underscore or whitespace
|
18
|
+
def search output_name_pattern
|
19
|
+
output.find_by_name(/#{output_name_pattern.to_s.sub '_','(_|\\s+)'}/i)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Command line interface to list output choices and select an output.
|
23
|
+
def prompt_for_output
|
24
|
+
devices_by_name = output.devices_by_name
|
25
|
+
names_by_number = {}
|
26
|
+
|
27
|
+
puts "Available MIDI outputs:"
|
28
|
+
devices_by_name.keys.each_with_index do |name,index|
|
29
|
+
number = index+1
|
30
|
+
names_by_number[number] = name
|
31
|
+
puts " #{number} => #{name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
print "Enter the number of the output to test: "
|
35
|
+
device = nil
|
36
|
+
loop do
|
37
|
+
begin
|
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
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MTK
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# An extension to {Collection}, which provides additional transformations for pitch-like collections.
|
5
|
+
#
|
6
|
+
module PitchCollection
|
7
|
+
include Collection
|
8
|
+
|
9
|
+
# Transpose all elements upward by the given interval
|
10
|
+
# @param interval_in_semitones [Numeric] an interval in semitones
|
11
|
+
def transpose interval_in_semitones
|
12
|
+
map{|elem| elem + interval_in_semitones }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Invert all elements around the given inversion point
|
16
|
+
# @param inversion_point [Numeric] the value around which all elements will be inverted (defaults to the first element in the collection)
|
17
|
+
def invert(inversion_point=first)
|
18
|
+
map{|elem| elem.invert(inversion_point) }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|