mtk 0.0.2 → 0.0.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 (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -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/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,29 @@
1
+ require 'citrus'
2
+
3
+ # @private
4
+ class Citrus::Match
5
+ def values_of(token_name)
6
+ captures[token_name].map{|token| token.value }
7
+ end
8
+ end
9
+
10
+ Citrus.load File.join(File.dirname(__FILE__),'mtk_grammar')
11
+
12
+ module MTK
13
+ module Lang
14
+
15
+ # Parser for the {file:lib/mtk/lang/mtk_grammar.citrus MTK grammar}
16
+ class Parser
17
+
18
+ def self.parse(syntax, root=:root, dump=false)
19
+ syntax = syntax.to_s.strip
20
+ return nil if syntax.empty?
21
+ matcher = ::MTK_Grammar.parse(syntax, :root => root)
22
+ puts matcher.dump if dump
23
+ matcher.value
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
data/lib/mtk/melody.rb ADDED
@@ -0,0 +1,94 @@
1
+ module MTK
2
+
3
+ # An ordered collection of {Pitch}es.
4
+ #
5
+ # The "horizontal" (sequential) pitch collection.
6
+ #
7
+ # Unlike the strict definition of melody, this class is fairly abstract and only models a succession of pitches.
8
+ # To create a true, playable melody one must combine an MTK::Melody and rhythms into a {Timeline}.
9
+ #
10
+ # @see Chord
11
+ #
12
+ class Melody
13
+ include Helpers::PitchCollection
14
+
15
+ attr_reader :pitches
16
+
17
+ # @param pitches [#to_a] the collection of pitches
18
+ # @see MTK#Melody
19
+ #
20
+ def initialize(pitches)
21
+ @pitches = pitches.to_a.clone.freeze
22
+ end
23
+
24
+ def self.from_pitch_classes(pitch_classes, start=Constants::Pitches::C4, max_distance=12)
25
+ pitch = start
26
+ pitches = []
27
+ pitch_classes.each do |pitch_class|
28
+ pitch = pitch.nearest(pitch_class)
29
+ pitch -= 12 if pitch > start+max_distance # keep within max_distance of start (default is one octave)
30
+ pitch += 12 if pitch < start-max_distance
31
+ pitches << pitch
32
+ end
33
+ new pitches
34
+ end
35
+
36
+ # @see Helper::Collection
37
+ def elements
38
+ @pitches
39
+ end
40
+
41
+ # Convert to an Array of pitches.
42
+ # @note this returns a mutable copy the underlying @pitches attribute, which is otherwise unmutable
43
+ alias :to_pitches :to_a
44
+
45
+ def self.from_a enumerable
46
+ new enumerable
47
+ end
48
+
49
+ def to_pitch_class_set(remove_duplicates=true)
50
+ PitchClassSet.new(remove_duplicates ? pitch_classes.uniq : pitch_classes)
51
+ end
52
+
53
+ def pitch_classes
54
+ @pitch_classes ||= @pitches.map{|p| p.pitch_class }
55
+ end
56
+
57
+ # @param other [#pitches, Enumerable]
58
+ def == other
59
+ if other.respond_to? :pitches
60
+ @pitches == other.pitches
61
+ elsif other.is_a? Enumerable
62
+ @pitches == other.to_a
63
+ else
64
+ @pitches == other
65
+ end
66
+ end
67
+
68
+ # Compare for equality, ignoring order and duplicates
69
+ # @param other [#pitches, Array, #to_a]
70
+ def =~ other
71
+ @normalized_pitches ||= @pitches.uniq.sort
72
+ @normalized_pitches == case
73
+ when other.respond_to?(:pitches) then other.pitches.uniq.sort
74
+ when (other.is_a? Array and other.frozen?) then other
75
+ when other.respond_to?(:to_a) then other.to_a.uniq.sort
76
+ else other
77
+ end
78
+ end
79
+
80
+ def to_s
81
+ '[' + @pitches.map{|pitch| pitch.to_s}.join(', ') + ']'
82
+ end
83
+
84
+ end
85
+
86
+ # Construct an ordered {Melody} that allows duplicates
87
+ # @see #Melody
88
+ # @see #Chord
89
+ def Melody(*anything)
90
+ Melody.new Helpers::Convert.to_pitches(*anything)
91
+ end
92
+ module_function :Melody
93
+
94
+ end
@@ -0,0 +1,144 @@
1
+ require 'ffi'
2
+
3
+ module MTK
4
+ module MIDI
5
+
6
+ # An output device for Apple's built-in "DLS" synthesizer on OS X
7
+ class DLSSynthDevice
8
+
9
+ module AudioToolbox
10
+ extend FFI::Library
11
+ ffi_lib '/System/Library/Frameworks/AudioToolbox.framework/Versions/Current/AudioToolbox'
12
+ ffi_lib '/System/Library/Frameworks/AudioUnit.framework/Versions/Current/AudioUnit'
13
+
14
+ class ComponentDescription < FFI::Struct
15
+ layout :componentType, :int,
16
+ :componentSubType, :int,
17
+ :componentManufacturer, :int,
18
+ :componentFlags, :int,
19
+ :componentFlagsMask, :int
20
+ end
21
+
22
+ def self.to_bytes(s)
23
+ bytes = 0
24
+ s.each_byte do |byte|
25
+ bytes <<= 8
26
+ bytes += byte
27
+ end
28
+ return bytes
29
+ end
30
+
31
+ AUDIO_UNIT_MANUFACTURER_APPLE = to_bytes('appl')
32
+ AUDIO_UNIT_TYPE_MUSIC_DEVICE = to_bytes('aumu')
33
+ AUDIO_UNIT_SUBTYPE_DLS_SYNTH = to_bytes('dls ')
34
+ AUDIO_UNIT_TYPE_OUTPUT = to_bytes('auou')
35
+ AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT = to_bytes('def ')
36
+
37
+ # int NewAUGraph(void *)
38
+ attach_function :NewAUGraph, [:pointer], :int
39
+
40
+ # int AUGraphAddNode(void *, ComponentDescription *, void *)
41
+ attach_function :AUGraphAddNode, [:pointer, :pointer, :pointer], :int
42
+
43
+ # int AUGraphOpen(void *)
44
+ attach_function :AUGraphOpen, [:pointer], :int
45
+
46
+ # int AUGraphConnectNodeInput(void *, void *, int, void *, int)
47
+ attach_function :AUGraphConnectNodeInput, [:pointer, :pointer, :int, :pointer, :int], :int
48
+
49
+ # int AUGraphNodeInfo(void *, void *, ComponentDescription *, void *)
50
+ attach_function :AUGraphNodeInfo, [:pointer, :pointer, :pointer, :pointer], :int
51
+
52
+ # int AUGraphInitialize(void *)
53
+ attach_function :AUGraphInitialize, [:pointer], :int
54
+
55
+ # int AUGraphStart(void *)
56
+ attach_function :AUGraphStart, [:pointer], :int
57
+
58
+ # int AUGraphStop(void *)
59
+ attach_function :AUGraphStop, [:pointer], :int
60
+
61
+ # int DisposeAUGraph(void *)
62
+ attach_function :DisposeAUGraph, [:pointer], :int
63
+
64
+ # void * CAShow(void *)
65
+ attach_function :CAShow, [:pointer], :void
66
+
67
+ # void * MusicDeviceMIDIEvent(void *, int, int, int, int)
68
+ attach_function :MusicDeviceMIDIEvent, [:pointer, :int, :int, :int, :int], :void
69
+
70
+ end
71
+
72
+
73
+ ##################################
74
+
75
+ def name
76
+ 'Apple DLS Synthesizer'
77
+ end
78
+
79
+
80
+ def require_noerr(action_description, &block)
81
+ if block.call != 0
82
+ fail "Failed to #{action_description}"
83
+ end
84
+ end
85
+
86
+
87
+ def open
88
+ synth_pointer = FFI::MemoryPointer.new(:pointer)
89
+ graph_pointer = FFI::MemoryPointer.new(:pointer)
90
+ synth_node_pointer = FFI::MemoryPointer.new(:pointer)
91
+ out_node_pointer = FFI::MemoryPointer.new(:pointer)
92
+
93
+ cd = AudioToolbox::ComponentDescription.new
94
+ cd[:componentManufacturer] = AudioToolbox::AUDIO_UNIT_MANUFACTURER_APPLE
95
+ cd[:componentFlags] = 0
96
+ cd[:componentFlagsMask] = 0
97
+
98
+ require_noerr('create AUGraph') { AudioToolbox.NewAUGraph(graph_pointer) }
99
+ @graph = graph_pointer.get_pointer(0)
100
+
101
+ cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_MUSIC_DEVICE
102
+ cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DLS_SYNTH
103
+ require_noerr('add synthNode') { AudioToolbox.AUGraphAddNode(@graph, cd, synth_node_pointer) }
104
+ synth_node = synth_node_pointer.get_pointer(0)
105
+
106
+ cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_OUTPUT
107
+ cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT
108
+ require_noerr('add outNode') { AudioToolbox.AUGraphAddNode(@graph, cd, out_node_pointer) }
109
+ out_node = out_node_pointer.get_pointer(0)
110
+
111
+ require_noerr('open graph') { AudioToolbox.AUGraphOpen(@graph) }
112
+
113
+ require_noerr('connect synth to out') { AudioToolbox.AUGraphConnectNodeInput(@graph, synth_node, 0, out_node, 0) }
114
+
115
+ require_noerr('graph info') { AudioToolbox.AUGraphNodeInfo(@graph, synth_node, nil, synth_pointer) }
116
+ @synth = synth_pointer.get_pointer(0)
117
+
118
+ require_noerr('init graph') { AudioToolbox.AUGraphInitialize(@graph) }
119
+ require_noerr('start graph') { AudioToolbox.AUGraphStart(@graph) }
120
+
121
+ # AudioToolbox.CAShow(@graph) # for debugging
122
+ end
123
+
124
+
125
+ def message(*args)
126
+ arg0 = args[0] || 0
127
+ arg1 = args[1] || 0
128
+ arg2 = args[2] || 0
129
+ AudioToolbox.MusicDeviceMIDIEvent(@synth, arg0, arg1, arg2, 0)
130
+ end
131
+
132
+
133
+ def close
134
+ if @graph
135
+ AudioToolbox.AUGraphStop(@graph)
136
+ AudioToolbox.DisposeAUGraph(@graph)
137
+ end
138
+ end
139
+ end
140
+
141
+ end
142
+ end
143
+
144
+
@@ -0,0 +1,62 @@
1
+ require 'mtk/midi/dls_synth_device'
2
+
3
+ module MTK
4
+ module MIDI
5
+
6
+ # Provides realtime MIDI output on OS X to the built-in "DLS" Synthesizer
7
+ # @note This class is optional and only available if you require 'mtk/midi/dls_synth_output'.
8
+ # It depends on the 'gamelan' gem.
9
+ class DLSSynthOutput < Output
10
+
11
+ public_class_method :new
12
+
13
+ def self.devices
14
+ @devices ||= [DLSSynthDevice.new]
15
+ end
16
+
17
+ def self.devices_by_name
18
+ @devices_by_name ||= {devices.first.name => devices.first}
19
+ end
20
+
21
+
22
+ ######################
23
+ protected
24
+
25
+ # (see Output#note_on)
26
+ def note_on(pitch, velocity, channel)
27
+ @device.message(0x90|channel, pitch, velocity)
28
+ end
29
+
30
+ # (see Output#note_off)
31
+ def note_off(pitch, velocity, channel)
32
+ @device.message(0x80|channel, pitch, velocity)
33
+ end
34
+
35
+ # (see Output#control)
36
+ def control(number, midi_value, channel)
37
+ @device.message(0xB0|channel, number, midi_value)
38
+ end
39
+
40
+ # (see Output#channel_pressure)
41
+ def channel_pressure(midi_value, channel)
42
+ @device.message(0xD0|channel, midi_value, 0)
43
+ end
44
+
45
+ # (see Output#poly_pressure)
46
+ def poly_pressure(pitch, midi_value, channel)
47
+ @device.message(0xA0|channel, pitch, midi_value)
48
+ end
49
+
50
+ # (see Output#bend)
51
+ def bend(midi_value, channel)
52
+ @device.message(0xE0|channel, midi_value & 127, (midi_value >> 7) & 127)
53
+ end
54
+
55
+ # (see Output#program)
56
+ def program(number, channel)
57
+ @device.message(0xC0|channel, number, 0)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
data/lib/mtk/midi/file.rb CHANGED
@@ -1,9 +1,11 @@
1
- require 'rubygems'
2
1
  require 'midilib'
3
2
 
4
3
  module MTK
5
4
  module MIDI
6
5
 
6
+ # MIDI file I/O: reads MIDI files into {Timeline}s and writes {Timeline}s to MIDI files.
7
+ # @note This class is optional and only available if you require 'mtk/midi/file'.
8
+ # It depends on the 'midilib' gem.
7
9
  class File
8
10
  def initialize file
9
11
  if file.respond_to? :path
@@ -15,7 +17,6 @@ module MTK
15
17
 
16
18
  # Read a MIDI file into an Array of {Timeline}s
17
19
  #
18
- # @param filepath [String, #path] path of the file to be written
19
20
  # @return [Timeline]
20
21
  #
21
22
  def to_timelines
@@ -27,25 +28,34 @@ module MTK
27
28
  pulses_per_beat = sequence.ppqn.to_f
28
29
  track_idx = -1
29
30
 
30
- for track in sequence
31
+ sequence.each do |track|
31
32
  track_idx += 1
32
33
  timeline = Timeline.new
33
34
  note_ons = {}
34
35
  #puts "TRACK #{track_idx}"
35
36
 
36
- for event in track
37
+ track.each do |event|
37
38
  #puts "#{event.class}: #{event} @#{event.time_from_start}"
39
+ time = (event.time_from_start)/pulses_per_beat
40
+
38
41
  case event
39
42
  when ::MIDI::NoteOn
40
- note_ons[event.note] = event
43
+ note_ons[event.note] = [time,event]
41
44
 
42
45
  when ::MIDI::NoteOff
43
- if on_event = note_ons.delete(event.note)
44
- time = (on_event.time_from_start)/pulses_per_beat
45
- duration = (event.time_from_start - on_event.time_from_start)/pulses_per_beat
46
- note = Note.from_midi event.note, on_event.velocity, duration
47
- timeline.add time, note
46
+ on_time,on_event = note_ons.delete(event.note)
47
+ if on_event
48
+ duration = time - on_time
49
+ note = MTK::Events::Note.from_midi(event.note, on_event.velocity, duration)
50
+ timeline.add on_time, note
48
51
  end
52
+
53
+ when ::MIDI::Controller, ::MIDI::PolyPressure, ::MIDI::ChannelPressure, ::MIDI::PitchBend, ::MIDI::ProgramChange
54
+ timeline.add time, MTK::Events::Parameter.from_midi(*event.data_as_bytes)
55
+
56
+ when ::MIDI::Tempo
57
+ # Not sure if event.tempo needs to be converted? TODO: test!
58
+ timeline.add time, MTK::Events::Parameter.new(:tempo, :value => event.tempo)
49
59
  end
50
60
  end
51
61
  timelines << timeline
@@ -56,43 +66,59 @@ module MTK
56
66
 
57
67
  def write(anything)
58
68
  case anything
59
- when Timeline then
60
- write_timeline(anything)
61
- when Array then
62
- write_timelines(anything)
63
- else
64
- raise "#{self.class}#write doesn't understand #{anything.class}"
69
+ when Timeline then write_timeline(anything)
70
+ when Enumerable then write_timelines(anything)
71
+ else raise "#{self.class}#write doesn't understand #{anything.class}"
65
72
  end
66
73
  end
67
74
 
68
75
  def write_timelines(timelines, parent_sequence=nil)
69
76
  sequence = parent_sequence || ::MIDI::Sequence.new
70
- for timeline in timelines
71
- write_timeline(timeline, sequence)
72
- end
77
+ timelines.each{|timeline| write_timeline(timeline, sequence) }
73
78
  write_to_disk sequence unless parent_sequence
74
79
  end
75
80
 
76
81
  # Write the Timeline as a MIDI file
77
82
  #
78
- # @param [Timeline]
83
+ # @param timeline [Timeline]
79
84
  def write_timeline(timeline, parent_sequence=nil)
80
85
  sequence = parent_sequence || ::MIDI::Sequence.new
81
86
  clock_rate = sequence.ppqn
82
87
  track = add_track sequence
83
- channel = 1
84
88
 
85
- for time,events in timeline
89
+ timeline.each do |time,events|
86
90
  time *= clock_rate
87
91
 
88
- for event in events
92
+ events.each do |event|
89
93
  next if event.rest?
90
94
 
91
- if event.is_a? Note
92
- pitch, velocity = event.pitch, event.velocity
93
- add_event track, time => note_on(channel, pitch, velocity)
94
- duration = event.duration_in_pulses(clock_rate)
95
- add_event track, time+duration => note_off(channel, pitch, velocity)
95
+ channel = event.channel || 0
96
+
97
+ case event.type
98
+ when :note
99
+ pitch, velocity = event.midi_pitch, event.velocity
100
+ add_event track, time => note_on(channel, pitch, velocity)
101
+ duration = event.duration_in_pulses(clock_rate)
102
+ add_event track, time+duration => note_off(channel, pitch, velocity)
103
+
104
+ when :control
105
+ add_event track, time => cc(channel, event.number, event.midi_value)
106
+
107
+ when :pressure
108
+ if event.number
109
+ add_event track, time => poly_pressure(channel, event.number, event.midi_value)
110
+ else
111
+ add_event track, time => channel_pressure(channel, event.midi_value)
112
+ end
113
+
114
+ when :bend
115
+ add_event track, time => pitch_bend(channel, event.midi_value)
116
+
117
+ when :program
118
+ add_event track, time => program(channel, event.midi_value)
119
+
120
+ when :tempo
121
+ add_event track, time => tempo(event.value)
96
122
  end
97
123
  end
98
124
  end
@@ -106,14 +132,15 @@ module MTK
106
132
  private
107
133
 
108
134
  def write_to_disk(sequence)
135
+ puts "Writing file #{@file}"
109
136
  ::File.open(@file, 'wb') { |f| sequence.write f }
110
137
  end
111
138
 
112
139
  def print_midi sequence
113
- for track in sequence
140
+ sequence.each do |track|
114
141
  puts "\n*** track \"#{track.name}\""
115
142
  puts "#{track.events.length} events"
116
- for event in track
143
+ track.each do |event|
117
144
  puts "#{event.to_s} (#{event.time_from_start})"
118
145
  end
119
146
  end
@@ -125,7 +152,7 @@ module MTK
125
152
  ::MIDI::Tempo.new(ms_per_quarter_note)
126
153
  end
127
154
 
128
- def program(program_number)
155
+ def program(channel, program_number)
129
156
  ::MIDI::ProgramChange.new(channel, program_number)
130
157
  end
131
158
 
@@ -138,7 +165,15 @@ module MTK
138
165
  end
139
166
 
140
167
  def cc(channel, controller, value)
141
- ::MIDI::Controller.new(channel, controller.to_i, value.to_i)
168
+ ::MIDI::Controller.new(channel, controller, value)
169
+ end
170
+
171
+ def poly_pressure(channel, pitch, value)
172
+ ::MIDI::PolyPressure(channel, pitch.to_i, value)
173
+ end
174
+
175
+ def channel_pressure(channel, value)
176
+ ::MIDI::ChannelPressure(channel, value)
142
177
  end
143
178
 
144
179
  def pitch_bend(channel, value)