jmtk 0.0.3.3-java

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 (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