mtk 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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)