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