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.
Files changed (102) hide show
  1. data/.yardopts +2 -2
  2. data/DEVELOPMENT_NOTES.md +20 -0
  3. data/README.md +9 -3
  4. data/Rakefile +47 -13
  5. data/bin/mtk +55 -20
  6. data/examples/crescendo.rb +4 -4
  7. data/examples/{drum_pattern1.rb → drum_pattern.rb} +8 -8
  8. data/examples/dynamic_pattern.rb +5 -5
  9. data/examples/gets_and_play.rb +3 -2
  10. data/examples/notation.rb +3 -3
  11. data/examples/play_midi.rb +4 -4
  12. data/examples/print_midi.rb +2 -2
  13. data/examples/random_tone_row.rb +3 -3
  14. data/examples/syntax_to_midi.rb +2 -2
  15. data/examples/test_output.rb +4 -5
  16. data/examples/tone_row_melody.rb +7 -5
  17. data/lib/mtk/core/duration.rb +213 -0
  18. data/lib/mtk/core/intensity.rb +158 -0
  19. data/lib/mtk/core/interval.rb +157 -0
  20. data/lib/mtk/core/pitch.rb +154 -0
  21. data/lib/mtk/core/pitch_class.rb +194 -0
  22. data/lib/mtk/events/event.rb +4 -4
  23. data/lib/mtk/events/note.rb +12 -12
  24. data/lib/mtk/events/timeline.rb +232 -0
  25. data/lib/mtk/groups/chord.rb +56 -0
  26. data/lib/mtk/{helpers → groups}/collection.rb +33 -1
  27. data/lib/mtk/groups/melody.rb +96 -0
  28. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  29. data/lib/mtk/{helpers → groups}/pitch_collection.rb +1 -1
  30. data/lib/mtk/{midi → io}/dls_synth_device.rb +3 -1
  31. data/lib/mtk/{midi → io}/dls_synth_output.rb +10 -10
  32. data/lib/mtk/{midi → io}/jsound_input.rb +2 -2
  33. data/lib/mtk/{midi → io}/jsound_output.rb +9 -9
  34. data/lib/mtk/{midi/file.rb → io/midi_file.rb} +13 -13
  35. data/lib/mtk/{midi/input.rb → io/midi_input.rb} +4 -4
  36. data/lib/mtk/{midi/output.rb → io/midi_output.rb} +8 -8
  37. data/lib/mtk/{helpers/lilypond.rb → io/notation.rb} +5 -5
  38. data/lib/mtk/{midi → io}/unimidi_input.rb +2 -2
  39. data/lib/mtk/{midi → io}/unimidi_output.rb +14 -9
  40. data/lib/mtk/{constants → lang}/durations.rb +11 -11
  41. data/lib/mtk/{constants → lang}/intensities.rb +11 -11
  42. data/lib/mtk/{constants → lang}/intervals.rb +17 -17
  43. data/lib/mtk/lang/mtk_grammar.citrus +9 -9
  44. data/lib/mtk/{constants → lang}/pitch_classes.rb +5 -5
  45. data/lib/mtk/{constants → lang}/pitches.rb +7 -7
  46. data/lib/mtk/{helpers → lang}/pseudo_constants.rb +1 -1
  47. data/lib/mtk/{variable.rb → lang/variable.rb} +1 -1
  48. data/lib/mtk/numeric_extensions.rb +40 -47
  49. data/lib/mtk/patterns/for_each.rb +1 -1
  50. data/lib/mtk/patterns/pattern.rb +3 -3
  51. data/lib/mtk/sequencers/event_builder.rb +16 -15
  52. data/lib/mtk/sequencers/legato_sequencer.rb +1 -1
  53. data/lib/mtk/sequencers/rhythmic_sequencer.rb +1 -1
  54. data/lib/mtk/sequencers/sequencer.rb +8 -8
  55. data/lib/mtk/sequencers/step_sequencer.rb +2 -2
  56. data/lib/mtk.rb +33 -39
  57. data/spec/mtk/{duration_spec.rb → core/duration_spec.rb} +3 -3
  58. data/spec/mtk/{intensity_spec.rb → core/intensity_spec.rb} +3 -3
  59. data/spec/mtk/{interval_spec.rb → core/interval_spec.rb} +1 -1
  60. data/spec/mtk/{pitch_class_spec.rb → core/pitch_class_spec.rb} +1 -1
  61. data/spec/mtk/{pitch_spec.rb → core/pitch_spec.rb} +8 -8
  62. data/spec/mtk/events/event_spec.rb +4 -4
  63. data/spec/mtk/events/note_spec.rb +8 -8
  64. data/spec/mtk/{timeline_spec.rb → events/timeline_spec.rb} +47 -47
  65. data/spec/mtk/{chord_spec.rb → groups/chord_spec.rb} +18 -16
  66. data/spec/mtk/{helpers → groups}/collection_spec.rb +3 -3
  67. data/spec/mtk/{melody_spec.rb → groups/melody_spec.rb} +36 -34
  68. data/spec/mtk/{pitch_class_set_spec.rb → groups/pitch_class_set_spec.rb} +57 -55
  69. data/spec/mtk/{midi/file_spec.rb → io/midi_file_spec.rb} +17 -17
  70. data/spec/mtk/{midi/output_spec.rb → io/midi_output_spec.rb} +6 -6
  71. data/spec/mtk/{constants → lang}/durations_spec.rb +1 -1
  72. data/spec/mtk/{constants → lang}/intensities_spec.rb +1 -1
  73. data/spec/mtk/{constants → lang}/intervals_spec.rb +1 -1
  74. data/spec/mtk/lang/parser_spec.rb +12 -6
  75. data/spec/mtk/{constants → lang}/pitch_classes_spec.rb +1 -1
  76. data/spec/mtk/{constants → lang}/pitches_spec.rb +1 -1
  77. data/spec/mtk/{helpers → lang}/pseudo_constants_spec.rb +2 -2
  78. data/spec/mtk/{variable_spec.rb → lang/variable_spec.rb} +4 -4
  79. data/spec/mtk/numeric_extensions_spec.rb +35 -55
  80. data/spec/mtk/patterns/for_each_spec.rb +1 -1
  81. data/spec/mtk/patterns/sequence_spec.rb +1 -1
  82. data/spec/mtk/sequencers/legato_sequencer_spec.rb +2 -2
  83. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +4 -4
  84. data/spec/mtk/sequencers/step_sequencer_spec.rb +5 -5
  85. data/spec/spec_helper.rb +7 -6
  86. metadata +75 -61
  87. data/ext/mkrf_conf.rb +0 -25
  88. data/lib/mtk/chord.rb +0 -55
  89. data/lib/mtk/duration.rb +0 -211
  90. data/lib/mtk/helpers/convert.rb +0 -36
  91. data/lib/mtk/helpers/output_selector.rb +0 -67
  92. data/lib/mtk/intensity.rb +0 -156
  93. data/lib/mtk/interval.rb +0 -155
  94. data/lib/mtk/melody.rb +0 -94
  95. data/lib/mtk/pitch.rb +0 -152
  96. data/lib/mtk/pitch_class.rb +0 -192
  97. data/lib/mtk/pitch_class_set.rb +0 -161
  98. data/lib/mtk/timeline.rb +0 -230
  99. data/spec/mtk/midi/jsound_input_spec.rb +0 -11
  100. data/spec/mtk/midi/jsound_output_spec.rb +0 -11
  101. data/spec/mtk/midi/unimidi_input_spec.rb +0 -11
  102. data/spec/mtk/midi/unimidi_output_spec.rb +0 -11
@@ -1,192 +0,0 @@
1
- module MTK
2
-
3
- # A set of all pitches that are an integer number of octaves apart.
4
- # A {Pitch} has the same PitchClass as the pitches one or more octaves away.
5
- # @see https://en.wikipedia.org/wiki/Pitch_class
6
- #
7
- class PitchClass
8
-
9
- # The normalized names of the 12 pitch classes in the chromatic scale.
10
- # The index of each {#name} is the pitch class's numeric {#value}.
11
- NAMES = %w( C Db D Eb E F Gb G Ab A Bb B ).freeze
12
-
13
- # All enharmonic names of the 12 pitch classes, including sharps, flats, double-sharps, and double-flats,
14
- # organized such that each index contains the allowed names of the pitch class with a {#value} equal to that index.
15
- # @see VALID_NAMES
16
- VALID_NAMES_BY_VALUE =
17
- [ # (valid names ), # value # normalized name
18
- %w( B# C Dbb ), # 0 # C
19
- %w( B## C# Db ), # 1 # Db
20
- %w( C## D Ebb ), # 2 # D
21
- %w( D# Eb Fbb ), # 3 # Eb
22
- %w( D## E Fb ), # 4 # E
23
- %w( E# F Gbb ), # 5 # F
24
- %w( E## F# Gb ), # 6 # Gb
25
- %w( F## G Abb ), # 7 # G
26
- %w( G# Ab ), # 8 # Ab
27
- %w( G## A Bbb ), # 9 # A
28
- %w( A# Bb Cbb ), # 10 # Bb
29
- %w( A## B Cb ) # 11 # B
30
- ].freeze
31
-
32
- # All valid enharmonic pitch class names in a flat list.
33
- # @see VALID_NAMES_BY_VALUE
34
- VALID_NAMES = VALID_NAMES_BY_VALUE.flatten.freeze
35
-
36
- # A mapping from valid names to the value of the pitch class with that name
37
- VALUES_BY_NAME = Hash[ # a map from a list of name,value pairs
38
- VALID_NAMES_BY_VALUE.map.with_index do |valid_names,value|
39
- valid_names.map{|name| [name,value] }
40
- end.flatten(1)
41
- ].freeze
42
-
43
-
44
- # The name of this pitch class.
45
- # One of the {NAMES} defined by this class.
46
- attr_reader :name
47
-
48
- # The value of this pitch class.
49
- # An integer from 0..11 that indexes this pitch class in {PITCH_CLASSES} and the {#name} in {NAMES}.
50
- attr_reader :value
51
-
52
-
53
- private ######
54
- # Even though new is a private_class_method, YARD gets confused so we temporarily go private
55
-
56
- def initialize(name, value)
57
- @name, @value = name, value
58
- end
59
- private_class_method :new
60
-
61
- @flyweight = {}
62
-
63
- public ######
64
-
65
-
66
- # Lookup a PitchClass by name or value.
67
- # @param name_or_value [String,Symbol,Numeric] one of {VALID_NAMES} or 0..12
68
- # @return the PitchClass representing the argument
69
- # @raise ArgumentError for arguments that cannot be converted to a PitchClass
70
- def self.[] name_or_value
71
- @flyweight[name_or_value] ||= case name_or_value
72
- when String,Symbol then from_name(name_or_value)
73
- when Numeric then from_value(name_or_value.round)
74
- else raise ArgumentError.new("PitchClass.[] doesn't understand #{name_or_value.class}")
75
- end
76
- end
77
-
78
- # Lookup a PitchClass by name.
79
- # @param name [String,#to_s] one of {VALID_NAMES} (case-insensitive)
80
- def self.from_name(name)
81
- @flyweight[name] ||= (
82
- valid_name = name.to_s.capitalize
83
- value = VALUES_BY_NAME[valid_name] or raise ArgumentError.new("Invalid PitchClass name: #{name}")
84
- new(valid_name,value)
85
- )
86
- end
87
-
88
- class << self
89
- alias from_s from_name
90
- end
91
-
92
- # All 12 pitch classes in the chromatic scale.
93
- # The index of each pitch class is the pitch class's numeric {#value}.
94
- PITCH_CLASSES = NAMES.map{|name| from_name name }.freeze
95
-
96
- # return the pitch class with the given integer value mod 12
97
- # @param value [Integer,#to_i]
98
- def self.from_value(value)
99
- PITCH_CLASSES[value.to_i % 12]
100
- end
101
-
102
- class << self
103
- alias from_i from_value
104
- end
105
-
106
- # return the pitch class with the given float rounded to the nearest integer, mod 12
107
- # @param value [Float,#to_f]
108
- def self.from_f(value)
109
- from_i value.to_f.round
110
- end
111
-
112
- # Compare 2 pitch classes for equal values.
113
- # @param other [PitchClass]
114
- # @return true if this pitch class's value is equal to the other pitch class's value
115
- def == other
116
- other.is_a? PitchClass and other.value == @value
117
- end
118
-
119
- # Compare a pitch class with another pitch class or integer value
120
- # @param other [PitchClass,#to_i]
121
- # @return -1, 0, or +1 depending on whether this pitch class's value is less than, equal to, or greater than the other object's integer value
122
- # @see http://ruby-doc.org/core-1.9.3/Comparable.html
123
- def <=> other
124
- @value <=> other.to_i
125
- end
126
-
127
- # This pitch class's normalized {#name}.
128
- # @see NAMES
129
- def to_s
130
- @name.to_s
131
- end
132
-
133
- # This pitch class's integer {#value}
134
- def to_i
135
- @value.to_i
136
- end
137
-
138
- # This pitch class's {#value} as a floating point number
139
- def to_f
140
- @value.to_f
141
- end
142
-
143
- # Transpose this pitch class by adding it's value to the value given (mod 12)
144
- # @param interval [PitchClass,Float,#to_f]
145
- def + interval
146
- new_value = (value + interval.to_f).round
147
- self.class.from_value new_value
148
- end
149
- alias transpose +
150
-
151
- # Transpose this pitch class by subtracing the given value from this value (mod 12)
152
- # @param interval [PitchClass,Float,#to_f]
153
- def - interval
154
- new_value = (value - interval.to_f).round
155
- self.class.from_value new_value
156
- end
157
-
158
- # Inverts (mirrors) the pitch class around the given center
159
- # @param center [PitchClass,Pitch,Float,#to_f] the value to "mirror" this pitch class around
160
- def invert(center)
161
- delta = (2*(center.to_f - value)).round
162
- self + delta
163
- end
164
-
165
- # the smallest interval in semitones that needs to be added to this PitchClass to reach the given PitchClass
166
- # @param pitch_class [PitchClass,#value]
167
- def distance_to(pitch_class)
168
- delta = (pitch_class.value - value) % 12
169
- if delta > 6
170
- delta -= 12
171
- elsif delta == 6 and to_i >= 6
172
- # this is a special edge case to prevent endlessly ascending pitch sequences when alternating between two pitch classes a tritone apart
173
- delta = -6
174
- end
175
- delta
176
- end
177
-
178
- end
179
-
180
- # Construct a {PitchClass} from any supported type
181
- # @param anything [PitchClass,String,Symbol,Numeric]
182
- def PitchClass(anything)
183
- case anything
184
- when Numeric then PitchClass.from_f(anything)
185
- when String, Symbol then PitchClass.from_s(anything)
186
- when PitchClass then anything
187
- else raise ArgumentError.new("PitchClass doesn't understand #{anything.class}")
188
- end
189
- end
190
- module_function :PitchClass
191
-
192
- end
@@ -1,161 +0,0 @@
1
- module MTK
2
-
3
- # An ordered collection of {PitchClass}es.
4
- #
5
- # Unlike a mathematical Set, a PitchClassSet is ordered and may contain duplicates.
6
- #
7
- # @see Melody
8
- # @see Chord
9
- #
10
- class PitchClassSet
11
- include Helpers::PitchCollection
12
-
13
- attr_reader :pitch_classes
14
-
15
- def self.random_row
16
- new(Constants::PitchClasses::PITCH_CLASSES.shuffle)
17
- end
18
-
19
- def self.all
20
- @all ||= new(Constants::PitchClasses::PITCH_CLASSES)
21
- end
22
-
23
- # @param pitch_classes [#to_a] the collection of pitch classes
24
- #
25
- # @see MTK#PitchClassSet
26
- #
27
- def initialize(pitch_classes)
28
- @pitch_classes = pitch_classes.to_a.clone.freeze
29
- end
30
-
31
- # @see Helper::Collection
32
- def elements
33
- @pitch_classes
34
- end
35
-
36
- # Convert to an Array of pitch_classes.
37
- # @note this returns a mutable copy the underlying @pitch_classes attribute, which is otherwise unmutable
38
- alias :to_pitch_classes :to_a
39
-
40
- def self.from_a enumerable
41
- new enumerable
42
- end
43
-
44
- def normal_order
45
- ordering = Array.new(@pitch_classes.uniq.sort)
46
- min_span, start_index_for_normal_order = nil, nil
47
-
48
- # check every rotation for the minimal span:
49
- size.times do |index|
50
- span = self.class.span_between ordering.first, ordering.last
51
-
52
- if min_span.nil? or span < min_span
53
- # best so far
54
- min_span = span
55
- start_index_for_normal_order = index
56
-
57
- elsif span == min_span
58
- # handle ties, minimize distance between first and second-to-last note, then first and third-to-last, etc
59
- span1, span2 = nil, nil
60
- tie_breaker = 1
61
- while span1 == span2 and tie_breaker < size
62
- span1 = self.class.span_between( ordering[0], ordering[-1 - tie_breaker] )
63
- span2 = self.class.span_between( ordering[start_index_for_normal_order], ordering[start_index_for_normal_order - tie_breaker] )
64
- tie_breaker -= 1
65
- end
66
- if span1 != span2
67
- # tie cannot be broken, pick the one starting with the lowest pitch class
68
- if ordering[0].to_i < ordering[start_index_for_normal_order].to_i
69
- start_index_for_normal_order = index
70
- end
71
- elsif span1 < span2
72
- start_index_for_normal_order = index
73
- end
74
-
75
- end
76
- ordering << ordering.shift # rotate
77
- end
78
-
79
- # we've rotated all the way around, so we now need to rotate back to the start index we just found:
80
- start_index_for_normal_order.times{ ordering << ordering.shift }
81
-
82
- ordering
83
- end
84
-
85
- def normal_form
86
- norder = normal_order
87
- first_pc_val = norder.first.to_i
88
- norder.map{|pitch_class| (pitch_class.to_i - first_pc_val) % 12 }
89
- end
90
-
91
- # the collection of elements present in both sets
92
- def intersection(other)
93
- self.class.from_a(to_a & other.to_a)
94
- end
95
-
96
- # the collection of all elements present in either set
97
- def union(other)
98
- self.class.from_a(to_a | other.to_a)
99
- end
100
-
101
- # the collection of elements from this set with any elements from the other set removed
102
- def difference(other)
103
- self.class.from_a(to_a - other.to_a)
104
- end
105
-
106
- # the collection of elements that are members of exactly one of the sets
107
- def symmetric_difference(other)
108
- union(other).difference( intersection(other) )
109
- end
110
-
111
- # the collection of elements that are not members of this set
112
- def complement
113
- self.class.all.difference(self)
114
- end
115
-
116
- # @param other [#pitch_classes, #to_a, Array]
117
- def == other
118
- if other.respond_to? :pitch_classes
119
- @pitch_classes == other.pitch_classes
120
- elsif other.respond_to? :to_a
121
- @pitch_classes == other.to_a
122
- else
123
- @pitch_classes == other
124
- end
125
- end
126
-
127
- # Compare for equality, ignoring order and duplicates
128
- # @param other [#pitch_classes, Array, #to_a]
129
- def =~ other
130
- @normalized_pitch_classes ||= @pitch_classes.uniq.sort
131
- @normalized_pitch_classes == case
132
- when other.respond_to?(:pitch_classes) then other.pitch_classes.uniq.sort
133
- when (other.is_a? Array and other.frozen?) then other
134
- when other.respond_to?(:to_a) then other.to_a.uniq.sort
135
- else other
136
- end
137
- end
138
-
139
- def to_s
140
- @pitch_classes.join(' ')
141
- end
142
-
143
- def inspect
144
- @pitch_classes.inspect
145
- end
146
-
147
- def self.span_between(pc1, pc2)
148
- (pc2.to_i - pc1.to_i) % 12
149
- end
150
-
151
- end
152
-
153
-
154
- # Construct a {PitchClassSet}
155
- # @see PitchClassSet#initialize
156
- def PitchClassSet(*anything)
157
- PitchClassSet.new Helpers::Convert.to_pitch_classes(*anything)
158
- end
159
- module_function :PitchClassSet
160
-
161
- end
data/lib/mtk/timeline.rb DELETED
@@ -1,230 +0,0 @@
1
- module MTK
2
-
3
- # A collection of timed events. The core data structure used to interface with input and output.
4
- #
5
- # Maps sorted floating point times to lists of events.
6
- #
7
- # Enumerable as [time,event_list] pairs.
8
- #
9
- class Timeline
10
- include Enumerable
11
-
12
- def initialize()
13
- @timeline = {}
14
- end
15
-
16
- class << self
17
- def from_a(enumerable)
18
- new.merge enumerable
19
- end
20
- alias from_hash from_a
21
- end
22
-
23
- def merge enumerable
24
- enumerable.each do |time,events|
25
- add(time,events)
26
- end
27
- self
28
- end
29
-
30
- def clear
31
- @timeline.clear
32
- self
33
- end
34
-
35
- def to_hash
36
- @timeline
37
- end
38
-
39
- def == other
40
- other = other.to_hash unless other.is_a? Hash
41
- @timeline == other
42
- end
43
-
44
- def [](time)
45
- @timeline[time.to_f]
46
- end
47
-
48
- def []=(time, events)
49
- time = time.to_f unless time.is_a? Numeric
50
- case events
51
- when nil?
52
- @timeline.delete time.to_f
53
- when Array
54
- @timeline[time.to_f] = events
55
- else
56
- @timeline[time.to_f] = [events]
57
- end
58
- end
59
-
60
- def add(time, event)
61
- events = @timeline[time.to_f]
62
- if events
63
- if event.is_a? Array
64
- events.concat event
65
- else
66
- events << event
67
- end
68
- else
69
- self[time] = event
70
- end
71
- end
72
-
73
- def delete(time)
74
- @timeline.delete(time.to_f)
75
- end
76
-
77
- def has_time? time
78
- @timeline.has_key? time.to_f
79
- end
80
-
81
- def times
82
- @timeline.keys.sort
83
- end
84
-
85
- def length
86
- last_time = times.last
87
- events = @timeline[last_time]
88
- last_time + events.map{|event| event.duration }.max
89
- end
90
-
91
- def empty?
92
- @timeline.empty?
93
- end
94
-
95
- def events
96
- times.map{|t| @timeline[t] }.flatten
97
- end
98
-
99
- def each
100
- # this is similar to @timeline.each, but by iterating over #times, we yield the events in chronological order
101
- times.each do |time|
102
- yield time, @timeline[time]
103
- end
104
- end
105
-
106
- # the original Enumerable#map implementation, which returns an Array
107
- alias enumerable_map map
108
-
109
- # Constructs a new Timeline by mapping each [time,event_list] pair
110
- # @see #map!
111
- def map &block
112
- self.class.from_a enumerable_map(&block)
113
- end
114
-
115
- # Perform #map in place
116
- # @see #map
117
- def map! &block
118
- mapped = enumerable_map(&block)
119
- clear
120
- merge mapped
121
- end
122
-
123
- # Map every individual event, without regard for the time at which is occurs
124
- def map_events
125
- mapped_timeline = Timeline.new
126
- self.each do |time,events|
127
- mapped_timeline[time] = events.map{|event| yield event }
128
- end
129
- mapped_timeline
130
- end
131
-
132
- # Map every individual event in place, without regard for the time at which is occurs
133
- def map_events!
134
- each do |time,events|
135
- self[time] = events.map{|event| yield event }
136
- end
137
- end
138
-
139
- def clone
140
- self.class.from_hash(to_hash)
141
- end
142
-
143
- def compact!
144
- @timeline.delete_if {|t,events| events.empty? }
145
- end
146
-
147
- def flatten
148
- flattened = Timeline.new
149
- self.each do |time,events|
150
- events.each do |event|
151
- if event.is_a? Timeline
152
- event.flatten.each do |subtime,subevent|
153
- flattened.add(time+subtime, subevent)
154
- end
155
- else
156
- flattened.add(time,event)
157
- end
158
- end
159
- end
160
- flattened
161
- end
162
-
163
- # @return a new Timeline where all times have been quantized to multiples of the given interval
164
- # @example timeline.quantize(0.5) # quantize to eight notes (assuming the beat is a quarter note)
165
- # @see quantize!
166
- def quantize interval
167
- map{|time,events| [self.class.quantize_time(time,interval), events] }
168
- end
169
-
170
- def quantize! interval
171
- map!{|time,events| [self.class.quantize_time(time,interval), events] }
172
- end
173
-
174
- # shifts all times by the given amount
175
- # @see #shift!
176
- # @see #shift_to
177
- def shift time_delta
178
- map{|time,events| [time+time_delta, events] }
179
- end
180
-
181
- # shifts all times in place by the given amount
182
- # @see #shift
183
- # @see #shift_to!
184
- def shift! time_delta
185
- map!{|time,events| [time+time_delta, events] }
186
- end
187
-
188
- # shifts the times so that the start of the timeline is at the given time
189
- # @see #shift_to!
190
- # @see #shift
191
- def shift_to absolute_time
192
- start = times.first
193
- if start
194
- shift absolute_time - start
195
- else
196
- clone
197
- end
198
- end
199
-
200
- # shifts the times in place so that the start of the timeline is at the given time
201
- # @see #shift_to
202
- # @see #shift!
203
- def shift_to! absolute_time
204
- start = times.first
205
- if start
206
- shift! absolute_time - start
207
- end
208
- self
209
- end
210
-
211
- def to_s
212
- times = self.times
213
- last = times.last
214
- width = sprintf("%d",last).length + 3 # nicely align the '=>' against the longest number
215
- times.map{|t| sprintf("%#{width}.2f",t)+" => #{@timeline[t].join ', '}" }.join "\n"
216
- end
217
-
218
- def inspect
219
- @timeline.inspect
220
- end
221
-
222
- def self.quantize_time time, interval
223
- upper = interval * (time.to_f/interval).ceil
224
- lower = upper - interval
225
- (time - lower) < (upper - time) ? lower : upper
226
- end
227
-
228
- end
229
-
230
- end
@@ -1,11 +0,0 @@
1
- begin
2
- require 'spec_helper'
3
- require 'mtk/midi/input'
4
- require 'mtk/midi/jsound_input'
5
-
6
- describe MTK::MIDI::JSoundInput do
7
-
8
- end
9
-
10
-
11
- rescue LoadError; end # only run the test when the required gems are available
@@ -1,11 +0,0 @@
1
- begin
2
- require 'spec_helper'
3
- require 'mtk/midi/output'
4
- require 'mtk/midi/jsound_output'
5
-
6
- describe MTK::MIDI::JSoundOutput do
7
-
8
- end
9
-
10
-
11
- rescue LoadError; end # only run the test when the required gems are available
@@ -1,11 +0,0 @@
1
- begin
2
- require 'spec_helper'
3
- require 'mtk/midi/input'
4
- require 'mtk/midi/unimidi_input'
5
-
6
- describe MTK::MIDI::UniMIDIInput do
7
-
8
- end
9
-
10
-
11
- rescue LoadError; end # only run the test when the required gems are available
@@ -1,11 +0,0 @@
1
- begin
2
- require 'spec_helper'
3
- require 'mtk/midi/output'
4
- require 'mtk/midi/unimidi_output'
5
-
6
- describe MTK::MIDI::UniMIDIOutput do
7
-
8
- end
9
-
10
-
11
- rescue LoadError; end # only run the test when the required gems are available