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,97 @@
1
+ module MTK
2
+ module IO
3
+
4
+ # Common behavior for realtime MIDI input.
5
+ #
6
+ class MIDIInput
7
+
8
+ class << self
9
+
10
+ def inherited(subclass)
11
+ available_input_types << subclass
12
+ end
13
+
14
+ def available_input_types
15
+ @available_input_types ||= []
16
+ end
17
+
18
+ def input_types_by_device
19
+ @input_types_by_device ||= (
20
+ available_input_types.each_with_object( Hash.new ) do |input_type,hash|
21
+ input_type.devices.each{|device| hash[device] = input_type }
22
+ end
23
+ )
24
+ end
25
+
26
+ # All available input devices.
27
+ def devices
28
+ @devices ||= available_input_types.map{|input_type| input_type.devices }.flatten
29
+ end
30
+
31
+ # Maps input device names to the input device.
32
+ def devices_by_name
33
+ @devices_by_name ||= (
34
+ available_input_types.each_with_object( Hash.new ) do |input_type,hash|
35
+ hash.merge!( input_type.devices_by_name )
36
+ end
37
+ )
38
+ end
39
+
40
+ def find_by_name(name)
41
+ if name.is_a? Regexp
42
+ matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
43
+ device = devices_by_name[matching_name]
44
+ else
45
+ device = devices_by_name[name.to_s]
46
+ end
47
+ open(device) if device
48
+ end
49
+
50
+ def open(device)
51
+ input_type = input_types_by_device[device]
52
+ input_type.new(device) if input_type
53
+ end
54
+ end
55
+
56
+
57
+ def initialize(input_device, options={})
58
+ @device = input_device
59
+ @device.open
60
+ @options = options
61
+ end
62
+ private_class_method :new
63
+
64
+
65
+ ########################
66
+ public
67
+
68
+ # The underlying output device implementation wrapped by this class.
69
+ # The device type depends on the platform.
70
+ attr_reader :device
71
+
72
+ def name
73
+ @device.name
74
+ end
75
+
76
+ def record
77
+ end
78
+
79
+ def stop
80
+ end
81
+
82
+ def to_timeline
83
+ Timeline.new
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
92
+ if RUBY_PLATFORM == 'java'
93
+ require 'mtk/io/jsound_input'
94
+ else
95
+ require 'mtk/io/unimidi_input'
96
+ end
97
+ end
@@ -0,0 +1,195 @@
1
+ require 'rbconfig'
2
+ require 'gamelan'
3
+
4
+ module MTK
5
+ module IO
6
+
7
+ # Provides a scheduler and common behavior for realtime MIDI output, using the gamelan gem for scheduling.
8
+ #
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
+ #
11
+ class MIDIOutput
12
+
13
+ class << self
14
+
15
+ def inherited(subclass)
16
+ available_output_types << subclass
17
+ end
18
+
19
+ def available_output_types
20
+ @available_output_types ||= []
21
+ end
22
+
23
+ def output_types_by_device
24
+ @output_types_by_device ||= (
25
+ available_output_types.each_with_object( Hash.new ) do |output_type,hash|
26
+ output_type.devices.each{|device| hash[device] = output_type }
27
+ end
28
+ )
29
+ end
30
+
31
+ # All available output devices.
32
+ def devices
33
+ @devices ||= available_output_types.map{|output_type| output_type.devices }.flatten
34
+ end
35
+
36
+ # Maps output device names to the output device.
37
+ def devices_by_name
38
+ @devices_by_name ||= (
39
+ available_output_types.each_with_object( Hash.new ) do |output_type,hash|
40
+ hash.merge!( output_type.devices_by_name )
41
+ end
42
+ )
43
+ end
44
+
45
+ def find_by_name(name)
46
+ if name.is_a? Regexp
47
+ matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
48
+ device = devices_by_name[matching_name]
49
+ else
50
+ device = devices_by_name[name.to_s]
51
+ end
52
+ open(device) if device
53
+ end
54
+
55
+ def open(device)
56
+ output_type = output_types_by_device[device]
57
+ output_type.new(device) if output_type
58
+ end
59
+ end
60
+
61
+
62
+ def initialize(output_device, options={})
63
+ @device = output_device
64
+ @device.open
65
+ @options = options
66
+ end
67
+ private_class_method :new
68
+
69
+
70
+ ########################
71
+ public
72
+
73
+ # The underlying output device implementation wrapped by this class.
74
+ # The device type depends on the platform.
75
+ attr_reader :device
76
+
77
+ def name
78
+ @device.name
79
+ end
80
+
81
+ def play(anything, options={})
82
+ timeline = case anything
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
+ else raise "#{self.class}.play() doesn't understand #{anything} (#{anything.class})"
87
+ end
88
+ timeline = timeline.flatten
89
+
90
+ scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
91
+ trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
92
+ in_background = options.fetch :background, false # default: don't run in background Thread
93
+ bpm = options.fetch :bmp, 120 # default: 120 beats per minute
94
+
95
+ @scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate
96
+
97
+ timeline.each do |time,events|
98
+ events.each do |event|
99
+ next if event.rest?
100
+
101
+ channel = event.channel || 0
102
+
103
+ case event.type
104
+ when :note
105
+ pitch = event.midi_pitch
106
+ velocity = event.velocity
107
+ duration = event.duration.to_f
108
+ @scheduler.at(time) { note_on(pitch,velocity,channel) }
109
+ @scheduler.at(time + duration) { note_off(pitch,velocity,channel) }
110
+
111
+ when :control
112
+ @scheduler.at(time) { control(event.number, event.midi_value, channel) }
113
+
114
+ when :pressure
115
+ if event.number
116
+ @scheduler.at(time) { poly_pressure(event.number, event.midi_value, channel) }
117
+ else
118
+ @scheduler.at(time) { channel_pressure(event.midi_value, channel) }
119
+ end
120
+
121
+ when :bend
122
+ @scheduler.at(time) { bend(event.midi_value, channel) }
123
+
124
+ when :program
125
+ @scheduler.at(time) { program(event.number, channel) }
126
+ end
127
+ end
128
+ end
129
+
130
+ end_time = timeline.times.last + trailing_buffer
131
+ @scheduler.at(end_time) { @scheduler.stop }
132
+
133
+ thread = @scheduler.run
134
+ thread.join if not in_background
135
+ end
136
+
137
+
138
+ ########################
139
+ protected
140
+
141
+ # these all return stubbed data for testing purposes
142
+
143
+ # Send a note on event to the MIDI output
144
+ def note_on(midi_pitch, velocity, channel)
145
+ [:note_on, midi_pitch, velocity, channel] # stubbed data for testing purposes
146
+ end
147
+
148
+ # Send a note off event to the MIDI output
149
+ def note_off(midi_pitch, velocity, channel)
150
+ [:note_off, midi_pitch, velocity, channel]
151
+ end
152
+
153
+ # Send a control change event to the MIDI output
154
+ def control(number, midi_value, channel)
155
+ [:control, number, midi_value, channel]
156
+ end
157
+
158
+ # Send a poly pressure event to the MIDI output.
159
+ def poly_pressure(midi_pitch, midi_value, channel)
160
+ [:poly_pressure, midi_pitch, midi_value, channel]
161
+ end
162
+
163
+ # Send a channel pressure event to the MIDI output.
164
+ def channel_pressure(midi_value, channel)
165
+ [:channel_pressure, midi_value, channel]
166
+ end
167
+
168
+ # Send a pitch bend event to the MIDI output.
169
+ def bend(midi_value, channel)
170
+ [:bend, midi_value, channel]
171
+ end
172
+
173
+ # Send a program change event to the MIDI output.
174
+ def program(number, channel)
175
+ [:program, number, channel]
176
+ end
177
+
178
+ end
179
+
180
+ end
181
+ end
182
+
183
+
184
+ unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
185
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
186
+ # We're running on OS X
187
+ require 'mtk/io/dls_synth_output'
188
+ end
189
+
190
+ if RUBY_PLATFORM == 'java'
191
+ require 'mtk/io/jsound_output'
192
+ else
193
+ require 'mtk/io/unimidi_output'
194
+ end
195
+ end
@@ -0,0 +1,162 @@
1
+ require 'tempfile'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+
5
+ module MTK
6
+ module IO
7
+
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
+ # @note To make notation graphics, {http://lilypond.org/download.html Lilypond} must be installed
11
+ # and you must follow the "Running on the command-line" instructions (found on the download page for
12
+ # your operating system). If the lilypond command is not on your PATH, set the environment variable LILYPOND_PATH
13
+ class Notation
14
+
15
+ LILYPOND_PATH = ENV['LILYPOND_PATH'] || 'lilypond'
16
+
17
+ VALID_FORMATS = %w( png pdf ps )
18
+
19
+
20
+ def initialize(file, options={})
21
+ @file = file
22
+ @options = options
23
+
24
+ @format = File.extname(file)[1..-1].downcase
25
+ raise ArgumentError.new("Invalid file format '#{@format}'") unless VALID_FORMATS.include? @format
26
+
27
+ @dpi = options[:dpi]
28
+ end
29
+
30
+ def self.open(file, options={})
31
+ new(file,options)
32
+ end
33
+
34
+
35
+ def write(timeline)
36
+ lilypond_syntax = syntax_for_timeline(timeline)
37
+ puts lilypond_syntax
38
+ puts "_____________________________"
39
+ Tempfile.open('mtk_lilypond') do |lilypond_file|
40
+ Dir.mktmpdir do |tmpdir|
41
+ # use the directory...
42
+ #open("#{dir}/foo", "w") { ... }
43
+
44
+ lilypond_file.write(lilypond_syntax)
45
+ lilypond_file.flush
46
+
47
+ cmd = ['lilypond', '-dbackend=eps', "-f#{@format}", "--output=\"#{tmpdir}\""]
48
+ cmd << "-dresolution=#{@dpi}" if @dpi
49
+ cmd << lilypond_file.path
50
+ cmd = cmd.join(' ')
51
+
52
+ puts cmd if $DEBUG
53
+ lilypond_command_output = `#{cmd}`
54
+ puts lilypond_command_output if $DEBUG
55
+
56
+ output_file = Dir["#{tmpdir}/*.#{@format}"].first
57
+
58
+ FileUtils.cp output_file, @file
59
+
60
+ puts "Wrote #{@file}"
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ ########################
67
+ private
68
+
69
+ QUANTIZATION_INTERVAL = 0.0625 # 64th note granularity
70
+
71
+ SYNTAX_PREFIX = '
72
+ \language "english"
73
+ \paper{
74
+ oddFooterMarkup=##f
75
+ oddHeaderMarkup=##f
76
+ }
77
+ \new PianoStaff {
78
+ \autochange {
79
+ '
80
+
81
+ SYNTAX_SUFFIX = '
82
+ }
83
+ }
84
+ '
85
+
86
+ def syntax_for_timeline(timeline)
87
+ quantized_timeline = timeline.flatten.quantize(QUANTIZATION_INTERVAL)
88
+ last_time = 0
89
+ last_duration = 0
90
+
91
+ s = ''
92
+ s << SYNTAX_PREFIX
93
+
94
+ for time,events in quantized_timeline
95
+
96
+ # handle rests between notes
97
+ delta = time - last_time
98
+ if delta > last_duration
99
+ s << 'r'+syntax_for_duration(delta - last_duration)
100
+ s << ' '
101
+ end
102
+
103
+ notes = events.find_all{|event| event.type == :note }
104
+
105
+ if notes.length > 1
106
+ # a chord
107
+ s << '<'
108
+ total_duration = 0
109
+ for note in notes
110
+ total_duration += note.duration
111
+ s << syntax_for_pitch(note.pitch)
112
+ s << ' '
113
+ end
114
+ s << '>'
115
+ duration = total_duration.to_f/notes.length
116
+ s << syntax_for_duration(duration)
117
+
118
+ else # a single note
119
+ note = notes.first
120
+ s << syntax_for_pitch(note.pitch)
121
+ duration = note.duration
122
+ s << syntax_for_duration(duration)
123
+ end
124
+
125
+ last_time = time
126
+ last_duration = duration
127
+ s << ' '
128
+ end
129
+
130
+ s << SYNTAX_SUFFIX
131
+ s
132
+ end
133
+
134
+ def syntax_for_pitch(pitch)
135
+ syntax = pitch.pitch_class.name.downcase
136
+ if syntax.length > 0
137
+ syntax = syntax[0] + syntax[1..-1].gsub('b','f') # .gsub('#','s') pitch class names never have '#'
138
+ end
139
+ oct = pitch.octave
140
+ while oct > 3
141
+ syntax << "'"
142
+ oct -= 1
143
+ end
144
+ while oct < 3
145
+ syntax << ","
146
+ oct += 1
147
+ end
148
+ syntax
149
+ end
150
+
151
+ def syntax_for_duration(duration)
152
+ # TODO: handle dots, triplets, and ties of arbitrary durations
153
+ duration = MTK::Events::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
154
+ syntax = (4.0/duration).round
155
+ syntax = 1 if syntax < 1
156
+ syntax.to_s
157
+ end
158
+
159
+ end
160
+
161
+ end
162
+ end