mtk 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +9 -0
- data/INTRO.md +73 -0
- data/LICENSE.txt +27 -0
- data/README.md +93 -18
- data/Rakefile +13 -1
- data/examples/crescendo.rb +20 -0
- data/examples/dynamic_pattern.rb +39 -0
- data/examples/play_midi.rb +19 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/tone_row_melody.rb +21 -0
- data/lib/mtk/_constants/durations.rb +80 -0
- data/lib/mtk/_constants/intensities.rb +81 -0
- data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
- data/lib/mtk/_constants/pitch_classes.rb +35 -0
- data/lib/mtk/_constants/pitches.rb +49 -0
- data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
- data/lib/mtk/event.rb +14 -5
- data/lib/mtk/helper/collection.rb +114 -0
- data/lib/mtk/helper/event_builder.rb +85 -0
- data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
- data/lib/mtk/lang/grammar.rb +17 -0
- data/lib/mtk/lang/mtk_grammar.citrus +60 -0
- data/lib/mtk/midi/file.rb +10 -15
- data/lib/mtk/midi/jsound_input.rb +68 -0
- data/lib/mtk/midi/jsound_output.rb +80 -0
- data/lib/mtk/note.rb +22 -3
- data/lib/mtk/pattern/abstract_pattern.rb +132 -0
- data/lib/mtk/pattern/choice.rb +25 -9
- data/lib/mtk/pattern/cycle.rb +51 -0
- data/lib/mtk/pattern/enumerator.rb +26 -0
- data/lib/mtk/pattern/function.rb +46 -0
- data/lib/mtk/pattern/lines.rb +60 -0
- data/lib/mtk/pattern/palindrome.rb +42 -0
- data/lib/mtk/pattern/sequence.rb +15 -50
- data/lib/mtk/pitch.rb +45 -6
- data/lib/mtk/pitch_class.rb +36 -35
- data/lib/mtk/pitch_class_set.rb +46 -14
- data/lib/mtk/pitch_set.rb +20 -31
- data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
- data/lib/mtk/sequencer/step_sequencer.rb +26 -0
- data/lib/mtk/timeline.rb +75 -22
- data/lib/mtk/transform/invertible.rb +15 -0
- data/lib/mtk/{util → transform}/mappable.rb +6 -2
- data/lib/mtk/transform/set_theory_operations.rb +34 -0
- data/lib/mtk/transform/transposable.rb +14 -0
- data/lib/mtk.rb +56 -22
- data/spec/mtk/_constants/durations_spec.rb +118 -0
- data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
- data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
- data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
- data/spec/mtk/_constants/pitches_spec.rb +52 -0
- data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
- data/spec/mtk/event_spec.rb +19 -0
- data/spec/mtk/helper/collection_spec.rb +291 -0
- data/spec/mtk/helper/event_builder_spec.rb +92 -0
- data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/grammar_spec.rb +100 -0
- data/spec/mtk/midi/file_spec.rb +41 -6
- data/spec/mtk/note_spec.rb +53 -3
- data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
- data/spec/mtk/pattern/choice_spec.rb +89 -3
- data/spec/mtk/pattern/cycle_spec.rb +133 -0
- data/spec/mtk/pattern/function_spec.rb +133 -0
- data/spec/mtk/pattern/lines_spec.rb +93 -0
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
- data/spec/mtk/pattern/palindrome_spec.rb +124 -0
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
- data/spec/mtk/pattern/sequence_spec.rb +128 -31
- data/spec/mtk/pitch_class_set_spec.rb +240 -7
- data/spec/mtk/pitch_class_spec.rb +84 -18
- data/spec/mtk/pitch_set_spec.rb +45 -10
- data/spec/mtk/pitch_spec.rb +59 -0
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
- data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
- data/spec/mtk/timeline_spec.rb +118 -15
- data/spec/spec_helper.rb +4 -3
- metadata +59 -22
- data/lib/mtk/chord.rb +0 -47
- data/lib/mtk/constants/dynamics.rb +0 -56
- data/lib/mtk/constants/pitch_classes.rb +0 -18
- data/lib/mtk/constants/pitches.rb +0 -24
- data/lib/mtk/pattern/note_sequence.rb +0 -60
- data/lib/mtk/pattern/pitch_sequence.rb +0 -22
- data/lib/mtk/patterns.rb +0 -4
- data/spec/mtk/chord_spec.rb +0 -74
- data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
- data/spec/mtk/constants/pitches_spec.rb +0 -23
- data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
- data/spec/mtk/pattern/pitch_sequence_spec.rb +0 -47
data/lib/mtk/pitch_class_set.rb
CHANGED
@@ -1,35 +1,39 @@
|
|
1
1
|
module MTK
|
2
2
|
|
3
|
-
#
|
3
|
+
# An ordered Set of PitchClasses, for 12-tone set-theory pitch analysis and manipulations
|
4
4
|
#
|
5
5
|
class PitchClassSet
|
6
6
|
|
7
|
-
include
|
7
|
+
include Helper::Collection
|
8
|
+
include Transform::Mappable
|
9
|
+
include Transform::Transposable
|
10
|
+
include Transform::Invertible
|
11
|
+
include Transform::SetTheoryOperations
|
8
12
|
|
9
13
|
attr_reader :pitch_classes
|
10
|
-
|
11
|
-
def
|
12
|
-
|
14
|
+
|
15
|
+
def self.random_row
|
16
|
+
new(PitchClasses::PITCH_CLASSES.shuffle)
|
13
17
|
end
|
14
18
|
|
15
|
-
def self.
|
16
|
-
new
|
19
|
+
def self.all
|
20
|
+
@all ||= new(PitchClasses::PITCH_CLASSES)
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
23
|
+
def initialize(pitch_classes)
|
24
|
+
@pitch_classes = pitch_classes.to_a.uniq.freeze
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
24
|
-
@pitch_classes
|
27
|
+
def elements
|
28
|
+
@pitch_classes
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
31
|
+
def self.from_a enumerable
|
32
|
+
new enumerable
|
29
33
|
end
|
30
34
|
|
31
35
|
def normal_order
|
32
|
-
ordering = Array.new(@pitch_classes)
|
36
|
+
ordering = Array.new(@pitch_classes.sort)
|
33
37
|
min_span, start_index_for_normal_order = nil, nil
|
34
38
|
|
35
39
|
# check every rotation for the minimal span:
|
@@ -86,6 +90,22 @@ module MTK
|
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
93
|
+
# Compare for equality, ignoring order
|
94
|
+
# @param other [#pitch_classes, #to_a, #sort, Array]
|
95
|
+
def =~ other
|
96
|
+
if other.is_a? Array and other.frozen?
|
97
|
+
@pitch_classes.sort == other
|
98
|
+
elsif other.respond_to? :pitch_classes
|
99
|
+
@pitch_classes.sort == other.pitch_classes.sort
|
100
|
+
elsif other.respond_to? :to_a
|
101
|
+
@pitch_classes.sort == other.to_a.sort
|
102
|
+
elsif other.respond_to? :sort
|
103
|
+
@pitch_classes.sort == other.sort
|
104
|
+
else
|
105
|
+
@pitch_classes.sort == other
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
89
109
|
def to_s
|
90
110
|
@pitch_classes.join(' ')
|
91
111
|
end
|
@@ -103,4 +123,16 @@ module MTK
|
|
103
123
|
end
|
104
124
|
|
105
125
|
end
|
126
|
+
|
127
|
+
# Construct a {PitchClassSet} from any supported type
|
128
|
+
def PitchClassSet(*anything)
|
129
|
+
anything = anything.first if anything.size == 1
|
130
|
+
case anything
|
131
|
+
when Array then PitchClassSet.new(anything.map{|elem| PitchClass(elem) })
|
132
|
+
when PitchClassSet then anything
|
133
|
+
else PitchClassSet.new([PitchClass(anything)])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
module_function :PitchClassSet
|
137
|
+
|
106
138
|
end
|
data/lib/mtk/pitch_set.rb
CHANGED
@@ -4,7 +4,10 @@ module MTK
|
|
4
4
|
#
|
5
5
|
class PitchSet
|
6
6
|
|
7
|
-
include
|
7
|
+
include Helper::Collection
|
8
|
+
include Transform::Mappable
|
9
|
+
include Transform::Transposable
|
10
|
+
include Transform::Invertible
|
8
11
|
|
9
12
|
attr_reader :pitches
|
10
13
|
|
@@ -12,16 +15,12 @@ module MTK
|
|
12
15
|
@pitches = pitches.to_a.uniq.sort.freeze
|
13
16
|
end
|
14
17
|
|
15
|
-
def
|
16
|
-
|
18
|
+
def elements
|
19
|
+
@pitches
|
17
20
|
end
|
18
21
|
|
19
|
-
def
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def each &block
|
24
|
-
@pitches.each &block
|
22
|
+
def self.from_a enumerable
|
23
|
+
new enumerable
|
25
24
|
end
|
26
25
|
|
27
26
|
def to_pitch_class_set
|
@@ -32,18 +31,7 @@ module MTK
|
|
32
31
|
@pitch_classes ||= @pitches.map{|p| p.pitch_class }.uniq
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
each_pitch_apply :+, semitones
|
37
|
-
end
|
38
|
-
|
39
|
-
def - semitones
|
40
|
-
each_pitch_apply :-, semitones
|
41
|
-
end
|
42
|
-
|
43
|
-
def invert(center_pitch=@pitches.first)
|
44
|
-
each_pitch_apply :invert, center_pitch
|
45
|
-
end
|
46
|
-
|
34
|
+
# generate a chord inversion (positive numbers move the lowest notes up an octave, negative moves the highest notes down)
|
47
35
|
def inversion(number)
|
48
36
|
number = number.to_i
|
49
37
|
pitch_set = Array.new(@pitches)
|
@@ -61,12 +49,8 @@ module MTK
|
|
61
49
|
self.class.new pitch_set
|
62
50
|
end
|
63
51
|
|
64
|
-
def include? pitch
|
65
|
-
@pitches.include? pitch
|
66
|
-
end
|
67
|
-
|
68
52
|
def nearest(pitch_class)
|
69
|
-
self
|
53
|
+
self.transpose @pitches.first.pitch_class.distance_to(pitch_class)
|
70
54
|
end
|
71
55
|
|
72
56
|
# @param other [#pitches, #to_a, Array]
|
@@ -84,12 +68,17 @@ module MTK
|
|
84
68
|
@pitches.inspect
|
85
69
|
end
|
86
70
|
|
87
|
-
|
88
|
-
protected
|
71
|
+
end
|
89
72
|
|
90
|
-
|
91
|
-
|
73
|
+
# Construct a {PitchSet} from any supported type
|
74
|
+
def PitchSet(*anything)
|
75
|
+
anything = anything.first if anything.size == 1
|
76
|
+
case anything
|
77
|
+
when Array then PitchSet.new(anything.map{|elem| Pitch(elem) })
|
78
|
+
when PitchSet then anything
|
79
|
+
else PitchSet.new([Pitch(anything)])
|
92
80
|
end
|
93
|
-
|
94
81
|
end
|
82
|
+
module_function :PitchSet
|
83
|
+
|
95
84
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencer
|
3
|
+
|
4
|
+
# A Sequencer produces {Timeline}s from a collection of {Pattern}s.
|
5
|
+
#
|
6
|
+
# @abstract Subclass and override {#advance} to implement a Sequencer.
|
7
|
+
#
|
8
|
+
class AbstractSequencer
|
9
|
+
|
10
|
+
# The maximum number of [time,event_list] entries that will be generated for the {Timeline}.
|
11
|
+
# nil means no maximum (be careful of infinite loops!)
|
12
|
+
attr_accessor :max_steps
|
13
|
+
|
14
|
+
# The maximum time (key) that will be generated for the {Timeline}.
|
15
|
+
# nil means no maximum (be careful of infinite loops!)
|
16
|
+
attr_accessor :max_time
|
17
|
+
|
18
|
+
# Used by {#to_timeline} to builds event lists from the results of #{Pattern::Enumerator#next} for the {Pattern}s in this Sequencer.
|
19
|
+
attr_reader :event_builder
|
20
|
+
|
21
|
+
# The current time offset for the sequencer. Used for the {Timeline} times.
|
22
|
+
attr_reader :time
|
23
|
+
|
24
|
+
# The current sequencer step index (the number of times-1 that {#next} has been called), or -1 if the sequencer has not yet started.
|
25
|
+
attr_reader :step
|
26
|
+
|
27
|
+
def initialize(patterns, options={})
|
28
|
+
@patterns = patterns
|
29
|
+
@max_steps = options[:max_steps]
|
30
|
+
@max_time = options[:max_time]
|
31
|
+
|
32
|
+
event_builder_class = options.fetch :event_builder, Helper::EventBuilder
|
33
|
+
@event_builder = event_builder_class.new(patterns, options)
|
34
|
+
rewind
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Produce a {Timeline} from the {Pattern}s in this Sequencer.
|
39
|
+
def to_timeline
|
40
|
+
rewind
|
41
|
+
timeline = Timeline.new
|
42
|
+
loop do
|
43
|
+
events = self.next
|
44
|
+
timeline[@time] = events if events
|
45
|
+
end
|
46
|
+
timeline
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# Advanced the step index and time, and return the next list of events built from the sequencer patterns.
|
51
|
+
# @note this is called automatically by {#to_timeline},
|
52
|
+
# so you can ignore this method unless you want to hack on sequencers at a lower level.
|
53
|
+
def next
|
54
|
+
if @step >= 0
|
55
|
+
advance!
|
56
|
+
raise StopIteration if @max_time and @time > @max_time
|
57
|
+
end
|
58
|
+
@step += 1
|
59
|
+
raise StopIteration if @max_steps and @step >= @max_steps
|
60
|
+
@event_builder.next
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Reset the sequencer and all its patterns.
|
65
|
+
# @note this is called automatically at the beginning of {#to_timeline},
|
66
|
+
# so you can ignore this method unless you want to hack on sequencers at a lower level.
|
67
|
+
def rewind
|
68
|
+
@time = 0
|
69
|
+
@step = -1
|
70
|
+
event_builder.rewind
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
########################
|
75
|
+
protected
|
76
|
+
|
77
|
+
# Advance @time to the next time for the {Timeline} being produced by {#to_timeline}
|
78
|
+
def advance!
|
79
|
+
@time += 1 # default behavior simply advances one beat at a time
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencer
|
3
|
+
|
4
|
+
# A Sequencer which uses a :rhythm type {Pattern} to determine the delta times between entries in the {Timeline}.
|
5
|
+
class RhythmicSequencer < AbstractSequencer
|
6
|
+
|
7
|
+
def initialize(patterns, options={})
|
8
|
+
patterns = patterns.clone
|
9
|
+
patterns.each_with_index do |pattern, index|
|
10
|
+
if pattern.type == :rhythm
|
11
|
+
@rhythm = pattern
|
12
|
+
patterns.delete_at index # so we don't enumerate the rhythm values in EventBuilder
|
13
|
+
end
|
14
|
+
end
|
15
|
+
super(patterns, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
########################
|
19
|
+
protected
|
20
|
+
|
21
|
+
# (see AbstractSequencer#advance!)
|
22
|
+
def advance!
|
23
|
+
@time += @rhythm.next
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MTK
|
2
|
+
module Sequencer
|
3
|
+
|
4
|
+
# A Sequencer which has a constant {#step_size} time between {Timeline} entries.
|
5
|
+
class StepSequencer < AbstractSequencer
|
6
|
+
|
7
|
+
# The time between entries in the {Timeline}.
|
8
|
+
attr_accessor :step_size
|
9
|
+
|
10
|
+
def initialize(patterns, options={})
|
11
|
+
super
|
12
|
+
@step_size = options.fetch :step_size, 1
|
13
|
+
end
|
14
|
+
|
15
|
+
########################
|
16
|
+
protected
|
17
|
+
|
18
|
+
# (see AbstractSequencer#advance!)
|
19
|
+
def advance!
|
20
|
+
@time += @step_size
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/mtk/timeline.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
module MTK
|
2
2
|
|
3
|
+
# A collection of timed events. The core data structure used to interface with input and output.
|
4
|
+
#
|
3
5
|
# Maps sorted times to lists of events.
|
4
6
|
#
|
5
|
-
# Enumerable as
|
7
|
+
# Enumerable as [time,event_list] pairs.
|
6
8
|
#
|
7
9
|
class Timeline
|
8
10
|
|
9
|
-
include Mappable
|
11
|
+
include Transform::Mappable
|
10
12
|
|
11
13
|
def initialize()
|
12
14
|
@timeline = {}
|
@@ -90,37 +92,32 @@ module MTK
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def each
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
yield time,event
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def each_time
|
102
|
-
times.each do |time|
|
103
|
-
events = @timeline[time]
|
104
|
-
yield time,events
|
95
|
+
# this is similar to @timeline.each, but by iterating over #times, we yield the events in chronological order
|
96
|
+
for time in times
|
97
|
+
yield time,@timeline[time]
|
105
98
|
end
|
106
99
|
end
|
107
100
|
|
108
101
|
def map! &block
|
102
|
+
# we use the enumerable_map that aliased by the Mappable module,
|
103
|
+
# because Mappable#map will create an extra timeline instance, which is unnecessary in this case
|
109
104
|
mapped = enumerable_map &block
|
110
105
|
clear
|
111
106
|
merge mapped
|
112
107
|
end
|
113
108
|
|
109
|
+
# Map every individual event, without regard for the time at which is occurs
|
114
110
|
def map_events
|
115
111
|
mapped_timeline = Timeline.new
|
116
|
-
|
112
|
+
for time,events in self
|
117
113
|
mapped_timeline[time] = events.map{|event| yield event }
|
118
114
|
end
|
119
115
|
mapped_timeline
|
120
116
|
end
|
121
117
|
|
118
|
+
# Map every individual event in place, without regard for the time at which is occurs
|
122
119
|
def map_events!
|
123
|
-
|
120
|
+
for time,events in self
|
124
121
|
self[time] = events.map{|event| yield event }
|
125
122
|
end
|
126
123
|
end
|
@@ -135,18 +132,68 @@ module MTK
|
|
135
132
|
|
136
133
|
def flatten
|
137
134
|
flattened = Timeline.new
|
138
|
-
for time,
|
139
|
-
|
140
|
-
|
141
|
-
|
135
|
+
for time,events in self
|
136
|
+
for event in events
|
137
|
+
if event.is_a? Timeline
|
138
|
+
for subtime,subevent in event.flatten
|
139
|
+
flattened.add(time+subtime, subevent)
|
140
|
+
end
|
141
|
+
else
|
142
|
+
flattened.add(time,event)
|
142
143
|
end
|
143
|
-
else
|
144
|
-
flattened.add(time,event)
|
145
144
|
end
|
146
145
|
end
|
147
146
|
flattened
|
148
147
|
end
|
149
148
|
|
149
|
+
# @return a new Timeline where all times have been quantized to multiples of the given interval
|
150
|
+
# @example timeline.quantize(0.5) # quantize to eight notes (assuming the beat is a quarter note)
|
151
|
+
# @see quantize!
|
152
|
+
def quantize interval
|
153
|
+
map{|time,events| [self.class.quantize_time(time,interval), events] }
|
154
|
+
end
|
155
|
+
|
156
|
+
def quantize! interval
|
157
|
+
map!{|time,events| [self.class.quantize_time(time,interval), events] }
|
158
|
+
end
|
159
|
+
|
160
|
+
# shifts all times by the given amount
|
161
|
+
# @see #shift!
|
162
|
+
# @see #shift_to
|
163
|
+
def shift time_delta
|
164
|
+
map{|time,events| [time+time_delta, events] }
|
165
|
+
end
|
166
|
+
|
167
|
+
# shifts all times in place by the given amount
|
168
|
+
# @see #shift
|
169
|
+
# @see #shift_to!
|
170
|
+
def shift! time_delta
|
171
|
+
map!{|time,events| [time+time_delta, events] }
|
172
|
+
end
|
173
|
+
|
174
|
+
# shifts the times so that the start of the timeline is at the given time
|
175
|
+
# @see #shift_to!
|
176
|
+
# @see #shift
|
177
|
+
def shift_to absolute_time
|
178
|
+
start = times.first
|
179
|
+
if start
|
180
|
+
shift absolute_time - start
|
181
|
+
else
|
182
|
+
clone
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# shifts the times in place so that the start of the timeline is at the given time
|
187
|
+
# @see #shift_to
|
188
|
+
# @see #shift!
|
189
|
+
def shift_to! absolute_time
|
190
|
+
start = times.first
|
191
|
+
if start
|
192
|
+
shift! absolute_time - start
|
193
|
+
end
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
150
197
|
def to_s
|
151
198
|
times.map{|t| "#{t} => #{@timeline[t].join ', '}" }.join "\n"
|
152
199
|
end
|
@@ -155,6 +202,12 @@ module MTK
|
|
155
202
|
@timeline.inspect
|
156
203
|
end
|
157
204
|
|
205
|
+
def self.quantize_time time, interval
|
206
|
+
upper = interval * (time.to_f/interval).ceil
|
207
|
+
lower = upper - interval
|
208
|
+
(time - lower) < (upper - time) ? lower : upper
|
209
|
+
end
|
210
|
+
|
158
211
|
end
|
159
212
|
|
160
213
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MTK::Transform
|
2
|
+
|
3
|
+
# {Mappable} class whose elements can handle the :invert message
|
4
|
+
# @note Classes including this module should include either MTK::Collection or provide a #first method
|
5
|
+
module Invertible
|
6
|
+
|
7
|
+
# Invert all elements around the given inversion point
|
8
|
+
# @param inversion_point [Numeric] the value around which all elements will be inverted (defaults to the first element in the collection)
|
9
|
+
def invert(inversion_point=first)
|
10
|
+
map{|elem| elem.invert(inversion_point) }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -1,14 +1,18 @@
|
|
1
|
-
module MTK
|
1
|
+
module MTK::Transform
|
2
2
|
|
3
3
|
# Similar to Enumerable, but relies on the including Class's from_a method to
|
4
|
-
# provide an implementation of #map which returns an object of the same type
|
4
|
+
# provide an implementation of #map which returns an object of the same type.
|
5
5
|
module Mappable
|
6
6
|
include Enumerable
|
7
7
|
|
8
|
+
# the original Enumerable#map implementation, which returns an Array
|
8
9
|
alias enumerable_map map
|
10
|
+
|
11
|
+
# the overriden #map implementation, which returns an object of the same type
|
9
12
|
def map &block
|
10
13
|
self.class.from_a(enumerable_map &block)
|
11
14
|
end
|
12
15
|
|
13
16
|
end
|
17
|
+
|
14
18
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MTK::Transform
|
2
|
+
|
3
|
+
# {Helper::Collection} that supports set theory operations
|
4
|
+
module SetTheoryOperations
|
5
|
+
|
6
|
+
# the collection of elements present in both sets
|
7
|
+
def intersection(other)
|
8
|
+
self.class.from_a(to_a & other.to_a)
|
9
|
+
end
|
10
|
+
|
11
|
+
# the collection of all elements present in either set
|
12
|
+
def union(other)
|
13
|
+
self.class.from_a(to_a | other.to_a)
|
14
|
+
end
|
15
|
+
|
16
|
+
# the collection of elements from this set with any elements from the other set removed
|
17
|
+
def difference(other)
|
18
|
+
self.class.from_a(to_a - other.to_a)
|
19
|
+
end
|
20
|
+
|
21
|
+
# the collection of elements that are members of exactly one of the sets
|
22
|
+
def symmetric_difference(other)
|
23
|
+
union(other).difference( intersection(other) )
|
24
|
+
end
|
25
|
+
|
26
|
+
# the collection of elements that are not members of this set
|
27
|
+
# @note this method requires that the including class define the class method .all(), which returns the collection of all possible elements
|
28
|
+
def complement
|
29
|
+
self.class.all.difference(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MTK::Transform
|
2
|
+
|
3
|
+
# {Mappable} class whose elements can handle the :transpose message
|
4
|
+
module Transposable
|
5
|
+
|
6
|
+
# Transpose all elements upward by the given interval
|
7
|
+
# @param interval_in_semitones [Numeric] an interval in semitones
|
8
|
+
def transpose interval_in_semitones
|
9
|
+
map{|elem| elem + interval_in_semitones }
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/lib/mtk.rb
CHANGED
@@ -1,4 +1,41 @@
|
|
1
|
-
|
1
|
+
##############################################
|
2
|
+
# Description of modules for documentation:
|
3
|
+
|
4
|
+
# The top level module for this library
|
5
|
+
module MTK
|
6
|
+
|
7
|
+
# Internal helper classes used to avoid duplicating code in this library.
|
8
|
+
module Helper
|
9
|
+
end
|
10
|
+
|
11
|
+
# Classes that emit elements one at a time. Used by {Sequencer}s to construct {Timeline}s.
|
12
|
+
#
|
13
|
+
# The core interface for Pattern classes is {Pattern::Enumerator#next} and {Pattern::Enumerator#rewind}.
|
14
|
+
module Pattern
|
15
|
+
end
|
16
|
+
|
17
|
+
# Classes that assemble {Pattern}s into {Timeline}s.
|
18
|
+
module Sequencer
|
19
|
+
end
|
20
|
+
|
21
|
+
# Optional classes for the "MTK language", which let's you compose music via MTK without writing any Ruby code
|
22
|
+
module Lang
|
23
|
+
end
|
24
|
+
|
25
|
+
# Optional classes for MIDI input and output.
|
26
|
+
module MIDI
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'mtk/helper/collection'
|
32
|
+
require 'mtk/helper/pseudo_constants'
|
33
|
+
|
34
|
+
require 'mtk/transform/mappable'
|
35
|
+
require 'mtk/transform/transposable'
|
36
|
+
require 'mtk/transform/invertible'
|
37
|
+
require 'mtk/transform/set_theory_operations'
|
38
|
+
|
2
39
|
require 'mtk/pitch_class'
|
3
40
|
require 'mtk/pitch_class_set'
|
4
41
|
require 'mtk/pitch'
|
@@ -6,31 +43,28 @@ require 'mtk/pitch_set'
|
|
6
43
|
|
7
44
|
require 'mtk/event'
|
8
45
|
require 'mtk/note'
|
9
|
-
require 'mtk/chord'
|
10
46
|
require 'mtk/timeline'
|
11
47
|
|
12
|
-
require 'mtk/
|
13
|
-
require 'mtk/
|
14
|
-
require 'mtk/
|
15
|
-
require 'mtk/
|
16
|
-
require 'mtk/
|
48
|
+
require 'mtk/_constants/pitch_classes'
|
49
|
+
require 'mtk/_constants/pitches'
|
50
|
+
require 'mtk/_constants/intervals'
|
51
|
+
require 'mtk/_constants/intensities'
|
52
|
+
require 'mtk/_constants/durations'
|
17
53
|
|
18
|
-
require 'mtk/
|
54
|
+
require 'mtk/pattern/enumerator'
|
55
|
+
require 'mtk/pattern/abstract_pattern'
|
56
|
+
require 'mtk/pattern/sequence'
|
57
|
+
require 'mtk/pattern/cycle'
|
58
|
+
require 'mtk/pattern/choice'
|
59
|
+
require 'mtk/pattern/function'
|
60
|
+
require 'mtk/pattern/lines'
|
61
|
+
require 'mtk/pattern/palindrome'
|
19
62
|
|
63
|
+
require 'mtk/helper/event_builder'
|
64
|
+
require 'mtk/sequencer/abstract_sequencer'
|
65
|
+
require 'mtk/sequencer/step_sequencer'
|
66
|
+
require 'mtk/sequencer/rhythmic_sequencer'
|
20
67
|
|
21
|
-
|
22
|
-
# Description of modules for documentation:
|
68
|
+
require 'mtk/_numeric_extensions'
|
23
69
|
|
24
|
-
# The top level module for this library
|
25
|
-
module MTK
|
26
|
-
|
27
|
-
# Classes for MIDI input and output
|
28
|
-
module MIDI
|
29
|
-
end
|
30
|
-
|
31
|
-
# Classes that enumerate elements
|
32
|
-
module Pattern
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
36
70
|
|