mtk 0.0.3.2 → 0.0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +2 -2
- data/DEVELOPMENT_NOTES.md +20 -0
- data/README.md +9 -3
- data/Rakefile +47 -13
- data/bin/mtk +55 -20
- data/examples/crescendo.rb +4 -4
- data/examples/{drum_pattern1.rb → drum_pattern.rb} +8 -8
- data/examples/dynamic_pattern.rb +5 -5
- data/examples/gets_and_play.rb +3 -2
- data/examples/notation.rb +3 -3
- data/examples/play_midi.rb +4 -4
- data/examples/print_midi.rb +2 -2
- data/examples/random_tone_row.rb +3 -3
- data/examples/syntax_to_midi.rb +2 -2
- data/examples/test_output.rb +4 -5
- data/examples/tone_row_melody.rb +7 -5
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -0
- data/lib/mtk/events/event.rb +4 -4
- data/lib/mtk/events/note.rb +12 -12
- data/lib/mtk/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/{helpers → groups}/collection.rb +33 -1
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/{helpers → groups}/pitch_collection.rb +1 -1
- data/lib/mtk/{midi → io}/dls_synth_device.rb +3 -1
- data/lib/mtk/{midi → io}/dls_synth_output.rb +10 -10
- data/lib/mtk/{midi → io}/jsound_input.rb +2 -2
- data/lib/mtk/{midi → io}/jsound_output.rb +9 -9
- data/lib/mtk/{midi/file.rb → io/midi_file.rb} +13 -13
- data/lib/mtk/{midi/input.rb → io/midi_input.rb} +4 -4
- data/lib/mtk/{midi/output.rb → io/midi_output.rb} +8 -8
- data/lib/mtk/{helpers/lilypond.rb → io/notation.rb} +5 -5
- data/lib/mtk/{midi → io}/unimidi_input.rb +2 -2
- data/lib/mtk/{midi → io}/unimidi_output.rb +14 -9
- data/lib/mtk/{constants → lang}/durations.rb +11 -11
- data/lib/mtk/{constants → lang}/intensities.rb +11 -11
- data/lib/mtk/{constants → lang}/intervals.rb +17 -17
- data/lib/mtk/lang/mtk_grammar.citrus +9 -9
- data/lib/mtk/{constants → lang}/pitch_classes.rb +5 -5
- data/lib/mtk/{constants → lang}/pitches.rb +7 -7
- data/lib/mtk/{helpers → lang}/pseudo_constants.rb +1 -1
- data/lib/mtk/{variable.rb → lang/variable.rb} +1 -1
- data/lib/mtk/numeric_extensions.rb +40 -47
- data/lib/mtk/patterns/for_each.rb +1 -1
- data/lib/mtk/patterns/pattern.rb +3 -3
- data/lib/mtk/sequencers/event_builder.rb +16 -15
- data/lib/mtk/sequencers/legato_sequencer.rb +1 -1
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +1 -1
- data/lib/mtk/sequencers/sequencer.rb +8 -8
- data/lib/mtk/sequencers/step_sequencer.rb +2 -2
- data/lib/mtk.rb +33 -39
- data/spec/mtk/{duration_spec.rb → core/duration_spec.rb} +3 -3
- data/spec/mtk/{intensity_spec.rb → core/intensity_spec.rb} +3 -3
- data/spec/mtk/{interval_spec.rb → core/interval_spec.rb} +1 -1
- data/spec/mtk/{pitch_class_spec.rb → core/pitch_class_spec.rb} +1 -1
- data/spec/mtk/{pitch_spec.rb → core/pitch_spec.rb} +8 -8
- data/spec/mtk/events/event_spec.rb +4 -4
- data/spec/mtk/events/note_spec.rb +8 -8
- data/spec/mtk/{timeline_spec.rb → events/timeline_spec.rb} +47 -47
- data/spec/mtk/{chord_spec.rb → groups/chord_spec.rb} +18 -16
- data/spec/mtk/{helpers → groups}/collection_spec.rb +3 -3
- data/spec/mtk/{melody_spec.rb → groups/melody_spec.rb} +36 -34
- data/spec/mtk/{pitch_class_set_spec.rb → groups/pitch_class_set_spec.rb} +57 -55
- data/spec/mtk/{midi/file_spec.rb → io/midi_file_spec.rb} +17 -17
- data/spec/mtk/{midi/output_spec.rb → io/midi_output_spec.rb} +6 -6
- data/spec/mtk/{constants → lang}/durations_spec.rb +1 -1
- data/spec/mtk/{constants → lang}/intensities_spec.rb +1 -1
- data/spec/mtk/{constants → lang}/intervals_spec.rb +1 -1
- data/spec/mtk/lang/parser_spec.rb +12 -6
- data/spec/mtk/{constants → lang}/pitch_classes_spec.rb +1 -1
- data/spec/mtk/{constants → lang}/pitches_spec.rb +1 -1
- data/spec/mtk/{helpers → lang}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/{variable_spec.rb → lang/variable_spec.rb} +4 -4
- data/spec/mtk/numeric_extensions_spec.rb +35 -55
- data/spec/mtk/patterns/for_each_spec.rb +1 -1
- data/spec/mtk/patterns/sequence_spec.rb +1 -1
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +2 -2
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +4 -4
- data/spec/mtk/sequencers/step_sequencer_spec.rb +5 -5
- data/spec/spec_helper.rb +7 -6
- metadata +75 -61
- data/ext/mkrf_conf.rb +0 -25
- data/lib/mtk/chord.rb +0 -55
- data/lib/mtk/duration.rb +0 -211
- data/lib/mtk/helpers/convert.rb +0 -36
- data/lib/mtk/helpers/output_selector.rb +0 -67
- data/lib/mtk/intensity.rb +0 -156
- data/lib/mtk/interval.rb +0 -155
- data/lib/mtk/melody.rb +0 -94
- data/lib/mtk/pitch.rb +0 -152
- data/lib/mtk/pitch_class.rb +0 -192
- data/lib/mtk/pitch_class_set.rb +0 -161
- data/lib/mtk/timeline.rb +0 -230
- data/spec/mtk/midi/jsound_input_spec.rb +0 -11
- data/spec/mtk/midi/jsound_output_spec.rb +0 -11
- data/spec/mtk/midi/unimidi_input_spec.rb +0 -11
- data/spec/mtk/midi/unimidi_output_spec.rb +0 -11
@@ -0,0 +1,96 @@
|
|
1
|
+
module MTK
|
2
|
+
module Groups
|
3
|
+
|
4
|
+
# An ordered collection of {Pitch}es.
|
5
|
+
#
|
6
|
+
# The "horizontal" (sequential) pitch collection.
|
7
|
+
#
|
8
|
+
# Unlike the strict definition of melody, this class is fairly abstract and only models a succession of pitches.
|
9
|
+
# To create a true, playable melody one must combine an MTK::Melody and rhythms into a {Events::Timeline}.
|
10
|
+
#
|
11
|
+
# @see Chord
|
12
|
+
#
|
13
|
+
class Melody
|
14
|
+
include PitchCollection
|
15
|
+
|
16
|
+
attr_reader :pitches
|
17
|
+
|
18
|
+
# @param pitches [#to_a] the collection of pitches
|
19
|
+
# @see MTK#Melody
|
20
|
+
#
|
21
|
+
def initialize(pitches)
|
22
|
+
@pitches = pitches.to_a.clone.freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_pitch_classes(pitch_classes, start=MTK::Lang::Pitches::C4, max_distance=12)
|
26
|
+
pitch = start
|
27
|
+
pitches = []
|
28
|
+
pitch_classes.each do |pitch_class|
|
29
|
+
pitch = pitch.nearest(pitch_class)
|
30
|
+
pitch -= 12 if pitch > start+max_distance # keep within max_distance of start (default is one octave)
|
31
|
+
pitch += 12 if pitch < start-max_distance
|
32
|
+
pitches << pitch
|
33
|
+
end
|
34
|
+
new pitches
|
35
|
+
end
|
36
|
+
|
37
|
+
# @see Helper::Collection
|
38
|
+
def elements
|
39
|
+
@pitches
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert to an Array of pitches.
|
43
|
+
# @note this returns a mutable copy the underlying @pitches attribute, which is otherwise unmutable
|
44
|
+
alias :to_pitches :to_a
|
45
|
+
|
46
|
+
def self.from_a enumerable
|
47
|
+
new enumerable
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_pitch_class_set(remove_duplicates=true)
|
51
|
+
PitchClassSet.new(remove_duplicates ? pitch_classes.uniq : pitch_classes)
|
52
|
+
end
|
53
|
+
|
54
|
+
def pitch_classes
|
55
|
+
@pitch_classes ||= @pitches.map{|p| p.pitch_class }
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param other [#pitches, Enumerable]
|
59
|
+
def == other
|
60
|
+
if other.respond_to? :pitches
|
61
|
+
@pitches == other.pitches
|
62
|
+
elsif other.is_a? Enumerable
|
63
|
+
@pitches == other.to_a
|
64
|
+
else
|
65
|
+
@pitches == other
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Compare for equality, ignoring order and duplicates
|
70
|
+
# @param other [#pitches, Array, #to_a]
|
71
|
+
def =~ other
|
72
|
+
@normalized_pitches ||= @pitches.uniq.sort
|
73
|
+
@normalized_pitches == case
|
74
|
+
when other.respond_to?(:pitches) then other.pitches.uniq.sort
|
75
|
+
when (other.is_a? Array and other.frozen?) then other
|
76
|
+
when other.respond_to?(:to_a) then other.to_a.uniq.sort
|
77
|
+
else other
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
'[' + @pitches.map{|pitch| pitch.to_s}.join(', ') + ']'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Construct an ordered {MTK::Groups::Melody} that allows duplicates
|
89
|
+
# @see #MTK::Groups::Melody
|
90
|
+
# @see #MTK::Groups::Chord
|
91
|
+
def Melody(*anything)
|
92
|
+
MTK::Groups::Melody.new MTK::Groups.to_pitches(*anything)
|
93
|
+
end
|
94
|
+
module_function :Melody
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
module Groups
|
4
|
+
|
5
|
+
# An ordered collection of {PitchClass}es.
|
6
|
+
#
|
7
|
+
# Unlike a mathematical Set, a PitchClassSet is ordered and may contain duplicates.
|
8
|
+
#
|
9
|
+
# @see MTK::Groups::Melody
|
10
|
+
# @see MTK::Groups::Chord
|
11
|
+
#
|
12
|
+
class PitchClassSet
|
13
|
+
include PitchCollection
|
14
|
+
|
15
|
+
attr_reader :pitch_classes
|
16
|
+
|
17
|
+
def self.random_row
|
18
|
+
new(MTK::Lang::PitchClasses::PITCH_CLASSES.shuffle)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.all
|
22
|
+
@all ||= new(MTK::Lang::PitchClasses::PITCH_CLASSES)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param pitch_classes [#to_a] the collection of pitch classes
|
26
|
+
#
|
27
|
+
# @see MTK#PitchClassSet
|
28
|
+
#
|
29
|
+
def initialize(pitch_classes)
|
30
|
+
@pitch_classes = pitch_classes.to_a.clone.freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see Helper::Collection
|
34
|
+
def elements
|
35
|
+
@pitch_classes
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert to an Array of pitch_classes.
|
39
|
+
# @note this returns a mutable copy the underlying @pitch_classes attribute, which is otherwise unmutable
|
40
|
+
alias :to_pitch_classes :to_a
|
41
|
+
|
42
|
+
def self.from_a enumerable
|
43
|
+
new enumerable
|
44
|
+
end
|
45
|
+
|
46
|
+
def normal_order
|
47
|
+
ordering = Array.new(@pitch_classes.uniq.sort)
|
48
|
+
min_span, start_index_for_normal_order = nil, nil
|
49
|
+
|
50
|
+
# check every rotation for the minimal span:
|
51
|
+
size.times do |index|
|
52
|
+
span = self.class.span_between ordering.first, ordering.last
|
53
|
+
|
54
|
+
if min_span.nil? or span < min_span
|
55
|
+
# best so far
|
56
|
+
min_span = span
|
57
|
+
start_index_for_normal_order = index
|
58
|
+
|
59
|
+
elsif span == min_span
|
60
|
+
# handle ties, minimize distance between first and second-to-last note, then first and third-to-last, etc
|
61
|
+
span1, span2 = nil, nil
|
62
|
+
tie_breaker = 1
|
63
|
+
while span1 == span2 and tie_breaker < size
|
64
|
+
span1 = self.class.span_between( ordering[0], ordering[-1 - tie_breaker] )
|
65
|
+
span2 = self.class.span_between( ordering[start_index_for_normal_order], ordering[start_index_for_normal_order - tie_breaker] )
|
66
|
+
tie_breaker -= 1
|
67
|
+
end
|
68
|
+
if span1 != span2
|
69
|
+
# tie cannot be broken, pick the one starting with the lowest pitch class
|
70
|
+
if ordering[0].to_i < ordering[start_index_for_normal_order].to_i
|
71
|
+
start_index_for_normal_order = index
|
72
|
+
end
|
73
|
+
elsif span1 < span2
|
74
|
+
start_index_for_normal_order = index
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
ordering << ordering.shift # rotate
|
79
|
+
end
|
80
|
+
|
81
|
+
# we've rotated all the way around, so we now need to rotate back to the start index we just found:
|
82
|
+
start_index_for_normal_order.times{ ordering << ordering.shift }
|
83
|
+
|
84
|
+
ordering
|
85
|
+
end
|
86
|
+
|
87
|
+
def normal_form
|
88
|
+
norder = normal_order
|
89
|
+
first_pc_val = norder.first.to_i
|
90
|
+
norder.map{|pitch_class| (pitch_class.to_i - first_pc_val) % 12 }
|
91
|
+
end
|
92
|
+
|
93
|
+
# the collection of elements present in both sets
|
94
|
+
def intersection(other)
|
95
|
+
self.class.from_a(to_a & other.to_a)
|
96
|
+
end
|
97
|
+
|
98
|
+
# the collection of all elements present in either set
|
99
|
+
def union(other)
|
100
|
+
self.class.from_a(to_a | other.to_a)
|
101
|
+
end
|
102
|
+
|
103
|
+
# the collection of elements from this set with any elements from the other set removed
|
104
|
+
def difference(other)
|
105
|
+
self.class.from_a(to_a - other.to_a)
|
106
|
+
end
|
107
|
+
|
108
|
+
# the collection of elements that are members of exactly one of the sets
|
109
|
+
def symmetric_difference(other)
|
110
|
+
union(other).difference( intersection(other) )
|
111
|
+
end
|
112
|
+
|
113
|
+
# the collection of elements that are not members of this set
|
114
|
+
def complement
|
115
|
+
self.class.all.difference(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param other [#pitch_classes, #to_a, Array]
|
119
|
+
def == other
|
120
|
+
if other.respond_to? :pitch_classes
|
121
|
+
@pitch_classes == other.pitch_classes
|
122
|
+
elsif other.respond_to? :to_a
|
123
|
+
@pitch_classes == other.to_a
|
124
|
+
else
|
125
|
+
@pitch_classes == other
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Compare for equality, ignoring order and duplicates
|
130
|
+
# @param other [#pitch_classes, Array, #to_a]
|
131
|
+
def =~ other
|
132
|
+
@normalized_pitch_classes ||= @pitch_classes.uniq.sort
|
133
|
+
@normalized_pitch_classes == case
|
134
|
+
when other.respond_to?(:pitch_classes) then other.pitch_classes.uniq.sort
|
135
|
+
when (other.is_a? Array and other.frozen?) then other
|
136
|
+
when other.respond_to?(:to_a) then other.to_a.uniq.sort
|
137
|
+
else other
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
@pitch_classes.join(' ')
|
143
|
+
end
|
144
|
+
|
145
|
+
def inspect
|
146
|
+
@pitch_classes.inspect
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.span_between(pc1, pc2)
|
150
|
+
(pc2.to_i - pc1.to_i) % 12
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Construct a {Groups::PitchClassSet}
|
157
|
+
# @see Groups::PitchClassSet#initialize
|
158
|
+
def PitchClassSet(*anything)
|
159
|
+
MTK::Groups::PitchClassSet.new MTK::Groups.to_pitch_classes(*anything)
|
160
|
+
end
|
161
|
+
module_function :PitchClassSet
|
162
|
+
|
163
|
+
end
|
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'ffi'
|
2
2
|
|
3
3
|
module MTK
|
4
|
-
module
|
4
|
+
module IO
|
5
5
|
|
6
6
|
# An output device for Apple's built-in "DLS" synthesizer on OS X
|
7
7
|
class DLSSynthDevice
|
8
8
|
|
9
|
+
# @private
|
9
10
|
module AudioToolbox
|
10
11
|
extend FFI::Library
|
11
12
|
ffi_lib '/System/Library/Frameworks/AudioToolbox.framework/Versions/Current/AudioToolbox'
|
12
13
|
ffi_lib '/System/Library/Frameworks/AudioUnit.framework/Versions/Current/AudioUnit'
|
13
14
|
|
15
|
+
# @private
|
14
16
|
class ComponentDescription < FFI::Struct
|
15
17
|
layout :componentType, :int,
|
16
18
|
:componentSubType, :int,
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require 'mtk/
|
1
|
+
require 'mtk/io/dls_synth_device'
|
2
2
|
|
3
3
|
module MTK
|
4
|
-
module
|
4
|
+
module IO
|
5
5
|
|
6
6
|
# Provides realtime MIDI output on OS X to the built-in "DLS" Synthesizer
|
7
7
|
# @note This class is optional and only available if you require 'mtk/midi/dls_synth_output'.
|
8
8
|
# It depends on the 'gamelan' gem.
|
9
|
-
class DLSSynthOutput <
|
9
|
+
class DLSSynthOutput < MIDIOutput
|
10
10
|
|
11
11
|
public_class_method :new
|
12
12
|
|
@@ -22,37 +22,37 @@ module MTK
|
|
22
22
|
######################
|
23
23
|
protected
|
24
24
|
|
25
|
-
# (see
|
25
|
+
# (see MIDIOutput#note_on)
|
26
26
|
def note_on(pitch, velocity, channel)
|
27
27
|
@device.message(0x90|channel, pitch, velocity)
|
28
28
|
end
|
29
29
|
|
30
|
-
# (see
|
30
|
+
# (see MIDIOutput#note_off)
|
31
31
|
def note_off(pitch, velocity, channel)
|
32
32
|
@device.message(0x80|channel, pitch, velocity)
|
33
33
|
end
|
34
34
|
|
35
|
-
# (see
|
35
|
+
# (see MIDIOutput#control)
|
36
36
|
def control(number, midi_value, channel)
|
37
37
|
@device.message(0xB0|channel, number, midi_value)
|
38
38
|
end
|
39
39
|
|
40
|
-
# (see
|
40
|
+
# (see MIDIOutput#channel_pressure)
|
41
41
|
def channel_pressure(midi_value, channel)
|
42
42
|
@device.message(0xD0|channel, midi_value, 0)
|
43
43
|
end
|
44
44
|
|
45
|
-
# (see
|
45
|
+
# (see MIDIOutput#poly_pressure)
|
46
46
|
def poly_pressure(pitch, midi_value, channel)
|
47
47
|
@device.message(0xA0|channel, pitch, midi_value)
|
48
48
|
end
|
49
49
|
|
50
|
-
# (see
|
50
|
+
# (see MIDIOutput#bend)
|
51
51
|
def bend(midi_value, channel)
|
52
52
|
@device.message(0xE0|channel, midi_value & 127, (midi_value >> 7) & 127)
|
53
53
|
end
|
54
54
|
|
55
|
-
# (see
|
55
|
+
# (see MIDIOutput#program)
|
56
56
|
def program(number, channel)
|
57
57
|
@device.message(0xC0|channel, number, 0)
|
58
58
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'jsound'
|
2
2
|
|
3
3
|
module MTK
|
4
|
-
module
|
4
|
+
module IO
|
5
5
|
|
6
6
|
# Provides realtime MIDI input for JRuby via the jsound gem.
|
7
7
|
# @note This class is optional and only available if you require 'mtk/midi/jsound_input'.
|
8
8
|
# It depends on the 'jsound' gem.
|
9
|
-
class JSoundInput <
|
9
|
+
class JSoundInput < MIDIInput
|
10
10
|
|
11
11
|
public_class_method :new
|
12
12
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'jsound'
|
2
2
|
|
3
3
|
module MTK
|
4
|
-
module
|
4
|
+
module IO
|
5
5
|
|
6
6
|
# Provides realtime MIDI output for JRuby via the jsound and gamelan gems.
|
7
7
|
# @note This class is optional and only available if you require 'mtk/midi/jsound_output'.
|
8
8
|
# It depends on the 'jsound' and 'gamelan' gems.
|
9
|
-
class JSoundOutput <
|
9
|
+
class JSoundOutput < MIDIOutput
|
10
10
|
|
11
11
|
public_class_method :new
|
12
12
|
|
@@ -41,37 +41,37 @@ module MTK
|
|
41
41
|
######################
|
42
42
|
protected
|
43
43
|
|
44
|
-
# (see
|
44
|
+
# (see MIDIOutput#note_on)
|
45
45
|
def note_on(pitch, velocity, channel)
|
46
46
|
@generator.note_on(pitch, velocity, channel)
|
47
47
|
end
|
48
48
|
|
49
|
-
# (see
|
49
|
+
# (see MIDIOutput#note_off)
|
50
50
|
def note_off(pitch, velocity, channel)
|
51
51
|
@generator.note_off(pitch, velocity, channel)
|
52
52
|
end
|
53
53
|
|
54
|
-
# (see
|
54
|
+
# (see MIDIOutput#control)
|
55
55
|
def control(number, midi_value, channel)
|
56
56
|
@generator.control_change(number, midi_value, channel)
|
57
57
|
end
|
58
58
|
|
59
|
-
# (see
|
59
|
+
# (see MIDIOutput#channel_pressure)
|
60
60
|
def channel_pressure(midi_value, channel)
|
61
61
|
@generator.channel_pressure(midi_value, channel)
|
62
62
|
end
|
63
63
|
|
64
|
-
# (see
|
64
|
+
# (see MIDIOutput#poly_pressure)
|
65
65
|
def poly_pressure(pitch, midi_value, channel)
|
66
66
|
@generator.poly_pressure(pitch, midi_value, channel)
|
67
67
|
end
|
68
68
|
|
69
|
-
# (see
|
69
|
+
# (see MIDIOutput#bend)
|
70
70
|
def bend(midi_value, channel)
|
71
71
|
@generator.pitch_bend(midi_value, channel)
|
72
72
|
end
|
73
73
|
|
74
|
-
# (see
|
74
|
+
# (see MIDIOutput#program)
|
75
75
|
def program(number, channel)
|
76
76
|
@generator.program_change(number, channel)
|
77
77
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'midilib'
|
2
2
|
|
3
3
|
module MTK
|
4
|
-
module
|
4
|
+
module IO
|
5
5
|
|
6
|
-
# MIDI file I/O: reads MIDI files into {Timeline}s and writes {Timeline}s to MIDI files.
|
6
|
+
# MIDI file I/O: reads MIDI files into {Events::Timeline}s and writes {Events::Timeline}s to MIDI files.
|
7
7
|
# @note This class is optional and only available if you require 'mtk/midi/file'.
|
8
8
|
# It depends on the 'midilib' gem.
|
9
|
-
class
|
9
|
+
class MIDIFile
|
10
10
|
def initialize file
|
11
11
|
if file.respond_to? :path
|
12
12
|
@file = file.path
|
@@ -15,7 +15,7 @@ module MTK
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
# Read a MIDI file into an Array of {Timeline}s
|
18
|
+
# Read a MIDI file into an Array of {Events::Timeline}s
|
19
19
|
#
|
20
20
|
# @return [Timeline]
|
21
21
|
#
|
@@ -30,7 +30,7 @@ module MTK
|
|
30
30
|
|
31
31
|
sequence.each do |track|
|
32
32
|
track_idx += 1
|
33
|
-
timeline =
|
33
|
+
timeline = MTK::Events::Timeline.new
|
34
34
|
note_ons = {}
|
35
35
|
#puts "TRACK #{track_idx}"
|
36
36
|
|
@@ -46,7 +46,7 @@ module MTK
|
|
46
46
|
on_time,on_event = note_ons.delete(event.note)
|
47
47
|
if on_event
|
48
48
|
duration = time - on_time
|
49
|
-
note = MTK::Events::Note.from_midi(event.note, on_event.velocity, duration)
|
49
|
+
note = MTK::Events::Note.from_midi(event.note, on_event.velocity, duration, event.channel)
|
50
50
|
timeline.add on_time, note
|
51
51
|
end
|
52
52
|
|
@@ -66,7 +66,7 @@ module MTK
|
|
66
66
|
|
67
67
|
def write(anything)
|
68
68
|
case anything
|
69
|
-
when
|
69
|
+
when MTK::Events::Timeline then write_timeline(anything)
|
70
70
|
when Enumerable then write_timelines(anything)
|
71
71
|
else raise "#{self.class}#write doesn't understand #{anything.class}"
|
72
72
|
end
|
@@ -92,7 +92,7 @@ module MTK
|
|
92
92
|
events.each do |event|
|
93
93
|
next if event.rest?
|
94
94
|
|
95
|
-
channel = event.channel || 0
|
95
|
+
channel = (event.channel || 1) - 1 # midilib seems to count channels from 0, hence the -1
|
96
96
|
|
97
97
|
case event.type
|
98
98
|
when :note
|
@@ -132,7 +132,7 @@ module MTK
|
|
132
132
|
private
|
133
133
|
|
134
134
|
def write_to_disk(sequence)
|
135
|
-
puts "Writing file #{@file}"
|
135
|
+
puts "Writing file #{@file}" unless $__RUNNING_RSPEC_TESTS__
|
136
136
|
::File.open(@file, 'wb') { |f| sequence.write f }
|
137
137
|
end
|
138
138
|
|
@@ -198,12 +198,12 @@ module MTK
|
|
198
198
|
end
|
199
199
|
end
|
200
200
|
|
201
|
-
# Shortcut for MTK::
|
201
|
+
# Shortcut for MTK::IO::MIDIFile.new
|
202
202
|
# @note Only available if you require 'mtk/midi/file'
|
203
|
-
def
|
204
|
-
|
203
|
+
def MIDIFile(f)
|
204
|
+
::MTK::IO::MIDIFile.new(f)
|
205
205
|
end
|
206
|
-
module_function :
|
206
|
+
module_function :MIDIFile
|
207
207
|
|
208
208
|
end
|
209
209
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module MTK
|
2
|
-
module
|
2
|
+
module IO
|
3
3
|
|
4
4
|
# Common behavior for realtime MIDI input.
|
5
5
|
#
|
6
|
-
class
|
6
|
+
class MIDIInput
|
7
7
|
|
8
8
|
class << self
|
9
9
|
|
@@ -90,8 +90,8 @@ end
|
|
90
90
|
|
91
91
|
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
92
92
|
if RUBY_PLATFORM == 'java'
|
93
|
-
require 'mtk/
|
93
|
+
require 'mtk/io/jsound_input'
|
94
94
|
else
|
95
|
-
require 'mtk/
|
95
|
+
require 'mtk/io/unimidi_input'
|
96
96
|
end
|
97
97
|
end
|
@@ -2,13 +2,13 @@ require 'rbconfig'
|
|
2
2
|
require 'gamelan'
|
3
3
|
|
4
4
|
module MTK
|
5
|
-
module
|
5
|
+
module IO
|
6
6
|
|
7
7
|
# Provides a scheduler and common behavior for realtime MIDI output, using the gamelan gem for scheduling.
|
8
8
|
#
|
9
9
|
# @abstract Subclasses must provide {.devices}, {.devices_by_name}, {#note_on}, {#note_off}, {#control}, {#channel_pressure}, {#poly_pressure}, {#bend}, and {#program} to implement a MIDI output.
|
10
10
|
#
|
11
|
-
class
|
11
|
+
class MIDIOutput
|
12
12
|
|
13
13
|
class << self
|
14
14
|
|
@@ -80,9 +80,9 @@ module MTK
|
|
80
80
|
|
81
81
|
def play(anything, options={})
|
82
82
|
timeline = case anything
|
83
|
-
when MTK::Timeline then anything
|
84
|
-
when Hash then
|
85
|
-
when Enumerable,MTK::Events::Event then
|
83
|
+
when MTK::Events::Timeline then anything
|
84
|
+
when Hash then MTK::Events::Timeline.from_h anything
|
85
|
+
when Enumerable,MTK::Events::Event then MTK::Events::Timeline.from_h(0 => anything)
|
86
86
|
else raise "#{self.class}.play() doesn't understand #{anything} (#{anything.class})"
|
87
87
|
end
|
88
88
|
timeline = timeline.flatten
|
@@ -184,12 +184,12 @@ end
|
|
184
184
|
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
185
185
|
if RbConfig::CONFIG['host_os'] =~ /darwin/
|
186
186
|
# We're running on OS X
|
187
|
-
require 'mtk/
|
187
|
+
require 'mtk/io/dls_synth_output'
|
188
188
|
end
|
189
189
|
|
190
190
|
if RUBY_PLATFORM == 'java'
|
191
|
-
require 'mtk/
|
191
|
+
require 'mtk/io/jsound_output'
|
192
192
|
else
|
193
|
-
require 'mtk/
|
193
|
+
require 'mtk/io/unimidi_output'
|
194
194
|
end
|
195
195
|
end
|
@@ -3,14 +3,14 @@ require 'tmpdir'
|
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
module MTK
|
6
|
-
module
|
6
|
+
module IO
|
7
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/
|
8
|
+
# Uses {Events::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/io/lilypond'.
|
10
10
|
# @note To make notation graphics, {http://lilypond.org/download.html Lilypond} must be installed
|
11
11
|
# and you must follow the "Running on the command-line" instructions (found on the download page for
|
12
12
|
# your operating system). If the lilypond command is not on your PATH, set the environment variable LILYPOND_PATH
|
13
|
-
class
|
13
|
+
class Notation
|
14
14
|
|
15
15
|
LILYPOND_PATH = ENV['LILYPOND_PATH'] || 'lilypond'
|
16
16
|
|
@@ -150,7 +150,7 @@ module MTK
|
|
150
150
|
|
151
151
|
def syntax_for_duration(duration)
|
152
152
|
# TODO: handle dots, triplets, and ties of arbitrary durations
|
153
|
-
duration = MTK::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
|
153
|
+
duration = MTK::Events::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
|
154
154
|
syntax = (4.0/duration).round
|
155
155
|
syntax = 1 if syntax < 1
|
156
156
|
syntax.to_s
|
@@ -2,12 +2,12 @@ require 'unimidi'
|
|
2
2
|
require 'ostruct'
|
3
3
|
|
4
4
|
module MTK
|
5
|
-
module
|
5
|
+
module IO
|
6
6
|
|
7
7
|
# Provides realtime MIDI input for MRI/YARV Ruby via the unimidi gem.
|
8
8
|
# @note This class is optional and only available if you require 'mtk/midi/unimidi_input'.
|
9
9
|
# It depends on the 'unimidi' gem.
|
10
|
-
class UniMIDIInput <
|
10
|
+
class UniMIDIInput < MIDIInput
|
11
11
|
|
12
12
|
public_class_method :new
|
13
13
|
|