mtk 0.0.2 → 0.0.3
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.
- 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
|