jmtk 0.0.3.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. metadata +226 -0
@@ -0,0 +1,119 @@
1
+ module MTK
2
+
3
+ module Events
4
+
5
+ # An abstract musical event
6
+ # @abstract
7
+ class Event
8
+
9
+ # The type of event: :note, :control, :pressure, :bend, or :program
10
+ attr_reader :type
11
+
12
+ # The specific element effected by this type of event, when applicable.
13
+ # Depends on the event type. For example, the number of a :note type Event is the pitch,
14
+ # and the number of a :control type Event is the controller (CC) number.
15
+ # This value is nil for inapplicable event types.
16
+ attr_accessor :number
17
+
18
+ # The value of event.
19
+ # Depends on event type. For example, the value of a :note type Event is the intensity,
20
+ # and the value of a :control type Event is the controller (CC) value.
21
+ attr_accessor :value
22
+
23
+ # Duration of the Event in beats (e.g. 1.0 is a quarter note in 4/4 time signatures)
24
+ # @see length
25
+ # @see rest?
26
+ # @see instantaneous?
27
+ # @see duration_in_pulses
28
+ attr_reader :duration
29
+
30
+ def duration= duration
31
+ @duration = duration
32
+ @duration = ::MTK::Core::Duration[@duration || 0] unless @duration.is_a? ::MTK::Core::Duration
33
+ @duration
34
+ end
35
+
36
+ # The channel of the event, for multi-tracked events.
37
+ attr_accessor :channel
38
+
39
+
40
+ def initialize(type, options={})
41
+ @type = type
42
+ @value = options[:value]
43
+ @number = options[:number]
44
+ @duration = options.fetch(:duration, 0)
45
+ @duration = ::MTK::Core::Duration[@duration] unless @duration.is_a? ::MTK::Core::Duration
46
+ @channel = options[:channel]
47
+ end
48
+
49
+ def self.from_h(hash)
50
+ new(hash[:type], hash)
51
+ end
52
+
53
+ def to_h
54
+ hash = {:type => @type}
55
+ hash[:value] = @value unless @value.nil?
56
+ hash[:duration] = @duration unless @duration.nil?
57
+ hash[:number] = @number unless @number.nil?
58
+ hash[:channel] = @channel unless @channel.nil?
59
+ hash
60
+ end
61
+
62
+ def midi_value
63
+ if @value and @value.respond_to? :to_midi
64
+ @value.to_midi
65
+ else
66
+ value = @value
67
+ midi_value = (127 * (value || 0)).round
68
+ midi_value = 0 if midi_value < 0
69
+ midi_value = 127 if midi_value > 127
70
+ midi_value
71
+ end
72
+ end
73
+
74
+ def midi_value= value
75
+ @value = value/127.0
76
+ end
77
+
78
+ # The magnitude (absolute value) of the duration.
79
+ # Indicate the "real" duration for rests.
80
+ # @see rest?
81
+ def length
82
+ @duration.length
83
+ end
84
+
85
+ # By convention, any events with negative durations are a rest
86
+ def rest?
87
+ @duration.rest?
88
+ end
89
+
90
+ # By convention, any events with 0 duration are instantaneous
91
+ def instantaneous?
92
+ @duration.nil? or @duration == 0
93
+ end
94
+
95
+ # Convert duration to an integer number of MIDI pulses, given the pulses_per_beat
96
+ def duration_in_pulses(pulses_per_beat)
97
+ (length.to_f * pulses_per_beat).round
98
+ end
99
+
100
+ def == other
101
+ other.respond_to? :type and @type == other.type and
102
+ other.respond_to? :number and @number == other.number and
103
+ other.respond_to? :value and @value == other.value and
104
+ other.respond_to? :duration and @duration == other.duration and
105
+ other.respond_to? :channel and @channel == other.channel
106
+ end
107
+
108
+ def to_s
109
+ "Event(#@type" + (@number ? "[#@number]" : '') + ", #{sprintf '%.2f',@value}, #{sprintf '%.2f',@duration})"
110
+ end
111
+
112
+ def inspect
113
+ "Event(#@type" + (@number ? "[#@number]" : '') + ", #@value, #{@duration.to_f})"
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,112 @@
1
+ module MTK
2
+
3
+ module Events
4
+
5
+ # A musical {Event} defined by a {Pitch}, intensity, and duration
6
+ class Note < Event
7
+
8
+ DEFAULT_DURATION = MTK::Core::Duration[1]
9
+ DEFAULT_INTENSITY = MTK::Core::Intensity[0.75]
10
+
11
+ # Frequency of the note as a {Pitch}.
12
+ alias :pitch :number
13
+ alias :pitch= :number=
14
+
15
+ # Intensity of the note as a value in the range 0.0 - 1.0.
16
+ alias :intensity :value
17
+ alias :intensity= :value=
18
+
19
+ # intensity scaled to the MIDI range 0-127
20
+ alias :velocity :midi_value
21
+ alias :velocity= :midi_value=
22
+
23
+ def initialize(pitch, duration=DEFAULT_DURATION, intensity=DEFAULT_INTENSITY, channel=nil)
24
+ super :note, number:pitch, duration:duration, value:intensity, channel:channel
25
+ end
26
+
27
+ def self.from_h(hash)
28
+ new(hash[:pitch]||hash[:number], hash[:duration], hash[:intensity]||hash[:value], hash[:channel])
29
+ end
30
+
31
+ def to_h
32
+ super.merge({ pitch: @number, intensity: @value })
33
+ end
34
+
35
+ def self.from_midi(pitch, velocity, duration_in_beats, channel=0)
36
+ new( MTK::Lang::Pitches::PITCHES[pitch.to_i], MTK::Core::Duration[duration_in_beats], MTK::Core::Intensity[velocity/127.0], channel )
37
+ end
38
+
39
+ def midi_pitch
40
+ pitch.to_i
41
+ end
42
+
43
+ def transpose(interval)
44
+ self.pitch += interval
45
+ self
46
+ end
47
+
48
+ def invert(around_pitch)
49
+ self.pitch = self.pitch.invert(around_pitch)
50
+ self
51
+ end
52
+
53
+ def ==(other)
54
+ ( other.respond_to? :pitch and pitch == other.pitch and
55
+ other.respond_to? :intensity and intensity == other.intensity and
56
+ other.respond_to? :duration and duration == other.duration
57
+ ) or super
58
+ end
59
+
60
+ def to_s
61
+ "Note(#{@number}, #{@duration}, #{@value.to_percent}%)"
62
+ end
63
+
64
+ def inspect
65
+ "#<#{self.class}:#{object_id} @pitch=#{@number.inspect}, @duration=#{@duration.inspect}, @intensity=#{@value.inspect}>"
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ # Construct a {Events::Note} from a list of any supported type for the arguments: pitch, intensity, duration, channel
72
+ def Note(*anything)
73
+ anything = anything.first if anything.size == 1
74
+ case anything
75
+ when MTK::Events::Note then anything
76
+
77
+ when MTK::Core::Pitch then MTK::Events::Note.new(anything)
78
+
79
+ when Array
80
+ pitch = nil
81
+ duration = nil
82
+ intensity = nil
83
+ channel = nil
84
+ unknowns = []
85
+ anything.each do |item|
86
+ case item
87
+ when MTK::Core::Pitch then pitch = item
88
+ when MTK::Core::Duration then duration = item
89
+ when MTK::Core::Intensity then intensity = item
90
+ else unknowns << item
91
+ end
92
+ end
93
+
94
+ pitch = MTK.Pitch(unknowns.shift) if pitch.nil? and not unknowns.empty?
95
+ raise "MTK::Note() couldn't find a pitch in arguments: #{anything.inspect}" if pitch.nil?
96
+
97
+ duration = MTK.Duration(unknowns.shift) if duration.nil? and not unknowns.empty?
98
+ intensity = MTK.Intensity(unknowns.shift) if intensity.nil? and not unknowns.empty?
99
+ channel = unknowns.shift.to_i if channel.nil? and not unknowns.empty?
100
+
101
+ duration ||= MTK::Events::Note::DEFAULT_DURATION
102
+ intensity ||= MTK::Events::Note::DEFAULT_INTENSITY
103
+
104
+ MTK::Events::Note.new( pitch, duration, intensity, channel )
105
+
106
+ else
107
+ raise "MTK::Note() doesn't understand #{anything.class}"
108
+ end
109
+ end
110
+ module_function :Note
111
+
112
+ end
@@ -0,0 +1,54 @@
1
+ module MTK
2
+
3
+ module Events
4
+
5
+ class Parameter < Event
6
+
7
+ def self.from_midi(status, data1, data2)
8
+ if status.is_a? Array
9
+ type,channel = *status
10
+ else
11
+ type,channel = status & 0xF0, status & 0x0F
12
+ end
13
+ type, number, value = *(
14
+ case type
15
+ when 0xA0,:poly_pressure then [:pressure, data1, data2]
16
+ when 0xB0,:control_change then [:control, data1, data2]
17
+ when 0xC0,:program_change then [:program, data1]
18
+ when 0xD0,:channel_pressure then [:pressure, nil, data1] # no number means all notes on channel
19
+ when 0xE0,:pitch_bend then [:bend, nil, (data1 + (data2 << 7))]
20
+ else [:unknown, data1, data2]
21
+ end
22
+ )
23
+ if type == :bend
24
+ if value == 16383
25
+ value = 1.0 # special case since the math doesn't quite work out to convert to -1..1 for all values
26
+ else
27
+ value = (value / 8192.0) - 1.0
28
+ end
29
+ elsif value.is_a? Numeric
30
+ value /= 127.0
31
+ end
32
+ new type, :number => number, :value => value, :channel => channel
33
+ end
34
+
35
+ def midi_value
36
+ if @type == :bend
37
+ (16383*(@value+1)/2).round
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def to_s
44
+ "Parameter(#@type" + (@number ? "[#@number], " : ', ') + "#{sprintf '%.2f', @value || Float::NAN})"
45
+ end
46
+
47
+ def inspect
48
+ "Parameter(#@type" + (@number ? "[#@number], " : ', ') + "#@value)"
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,232 @@
1
+ module MTK
2
+ module Events
3
+
4
+ # A collection of timed events. The core data structure used to interface with input and output.
5
+ #
6
+ # Maps sorted floating point times to lists of events.
7
+ #
8
+ # Enumerable as [time,event_list] pairs.
9
+ #
10
+ class Timeline
11
+ include Enumerable
12
+
13
+ def initialize()
14
+ @timeline = {}
15
+ end
16
+
17
+ class << self
18
+ def from_a(enumerable)
19
+ new.merge enumerable
20
+ end
21
+ alias from_h from_a
22
+ end
23
+
24
+ def merge enumerable
25
+ enumerable.each do |time,events|
26
+ add(time,events)
27
+ end
28
+ self
29
+ end
30
+
31
+ def clear
32
+ @timeline.clear
33
+ self
34
+ end
35
+
36
+ def to_h
37
+ @timeline
38
+ end
39
+
40
+ def == other
41
+ other = other.to_h unless other.is_a? Hash
42
+ @timeline == other
43
+ end
44
+
45
+ def [](time)
46
+ @timeline[time.to_f]
47
+ end
48
+
49
+ def []=(time, events)
50
+ time = time.to_f unless time.is_a? Numeric
51
+ case events
52
+ when nil?
53
+ @timeline.delete time.to_f
54
+ when Array
55
+ @timeline[time.to_f] = events
56
+ else
57
+ @timeline[time.to_f] = [events]
58
+ end
59
+ end
60
+
61
+ def add(time, event)
62
+ events = @timeline[time.to_f]
63
+ if events
64
+ if event.is_a? Array
65
+ events.concat event
66
+ else
67
+ events << event
68
+ end
69
+ else
70
+ self[time] = event
71
+ end
72
+ end
73
+
74
+ def delete(time)
75
+ @timeline.delete(time.to_f)
76
+ end
77
+
78
+ def has_time? time
79
+ @timeline.has_key? time.to_f
80
+ end
81
+
82
+ def times
83
+ @timeline.keys.sort
84
+ end
85
+
86
+ def length
87
+ last_time = times.last
88
+ events = @timeline[last_time]
89
+ last_time + events.map{|event| event.duration }.max
90
+ end
91
+
92
+ def empty?
93
+ @timeline.empty?
94
+ end
95
+
96
+ def events
97
+ times.map{|t| @timeline[t] }.flatten
98
+ end
99
+
100
+ def each
101
+ # this is similar to @timeline.each, but by iterating over #times, we yield the events in chronological order
102
+ times.each do |time|
103
+ yield time, @timeline[time]
104
+ end
105
+ end
106
+
107
+ # the original Enumerable#map implementation, which returns an Array
108
+ alias enumerable_map map
109
+
110
+ # Constructs a new Timeline by mapping each [time,event_list] pair
111
+ # @see #map!
112
+ def map &block
113
+ self.class.from_a enumerable_map(&block)
114
+ end
115
+
116
+ # Perform #map in place
117
+ # @see #map
118
+ def map! &block
119
+ mapped = enumerable_map(&block)
120
+ clear
121
+ merge mapped
122
+ end
123
+
124
+ # Map every individual event, without regard for the time at which is occurs
125
+ def map_events
126
+ mapped_timeline = Timeline.new
127
+ self.each do |time,events|
128
+ mapped_timeline[time] = events.map{|event| yield event }
129
+ end
130
+ mapped_timeline
131
+ end
132
+
133
+ # Map every individual event in place, without regard for the time at which is occurs
134
+ def map_events!
135
+ each do |time,events|
136
+ self[time] = events.map{|event| yield event }
137
+ end
138
+ end
139
+
140
+ def clone
141
+ self.class.from_h(to_h)
142
+ end
143
+
144
+ def compact!
145
+ @timeline.delete_if {|t,events| events.empty? }
146
+ end
147
+
148
+ def flatten
149
+ flattened = Timeline.new
150
+ self.each do |time,events|
151
+ events.each do |event|
152
+ if event.is_a? Timeline
153
+ event.flatten.each do |subtime,subevent|
154
+ flattened.add(time+subtime, subevent)
155
+ end
156
+ else
157
+ flattened.add(time,event)
158
+ end
159
+ end
160
+ end
161
+ flattened
162
+ end
163
+
164
+ # @return a new Timeline where all times have been quantized to multiples of the given interval
165
+ # @example timeline.quantize(0.5) # quantize to eight notes (assuming the beat is a quarter note)
166
+ # @see quantize!
167
+ def quantize interval
168
+ map{|time,events| [self.class.quantize_time(time,interval), events] }
169
+ end
170
+
171
+ def quantize! interval
172
+ map!{|time,events| [self.class.quantize_time(time,interval), events] }
173
+ end
174
+
175
+ # shifts all times by the given amount
176
+ # @see #shift!
177
+ # @see #shift_to
178
+ def shift time_delta
179
+ map{|time,events| [time+time_delta, events] }
180
+ end
181
+
182
+ # shifts all times in place by the given amount
183
+ # @see #shift
184
+ # @see #shift_to!
185
+ def shift! time_delta
186
+ map!{|time,events| [time+time_delta, events] }
187
+ end
188
+
189
+ # shifts the times so that the start of the timeline is at the given time
190
+ # @see #shift_to!
191
+ # @see #shift
192
+ def shift_to absolute_time
193
+ start = times.first
194
+ if start
195
+ shift absolute_time - start
196
+ else
197
+ clone
198
+ end
199
+ end
200
+
201
+ # shifts the times in place so that the start of the timeline is at the given time
202
+ # @see #shift_to
203
+ # @see #shift!
204
+ def shift_to! absolute_time
205
+ start = times.first
206
+ if start
207
+ shift! absolute_time - start
208
+ end
209
+ self
210
+ end
211
+
212
+ def to_s
213
+ times = self.times
214
+ last = times.last
215
+ width = sprintf("%d",last).length + 3 # nicely align the '=>' against the longest number
216
+ times.map{|t| sprintf("%#{width}.2f",t)+" => #{@timeline[t].join ', '}" }.join "\n"
217
+ end
218
+
219
+ def inspect
220
+ @timeline.inspect
221
+ end
222
+
223
+ def self.quantize_time time, interval
224
+ upper = interval * (time.to_f/interval).ceil
225
+ lower = upper - interval
226
+ (time - lower) < (upper - time) ? lower : upper
227
+ end
228
+
229
+ end
230
+
231
+ end
232
+ end