midi-message 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module MIDIMessage
4
+
5
+ module Process
6
+
7
+ class Transpose
8
+
9
+ include Processor
10
+
11
+ attr_reader :factor, :property
12
+
13
+ def initialize(message, prop, factor, options = {})
14
+ @factor = factor
15
+ @message = message
16
+ @property = prop
17
+ initialize_processor(message)
18
+ end
19
+
20
+ def process
21
+ val = @message.send(@property)
22
+ @message.send("#{@property}=", val + @factor)
23
+ @message
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ module MIDIMessage
5
+
6
+ # common behavior amongst all Message types
7
+ module ShortMessage
8
+
9
+ attr_reader :name,
10
+ :status,
11
+ :verbose_name
12
+
13
+ def initialize_short_message(status_nibble_1, status_nibble_2)
14
+ @status = [status_nibble_1, status_nibble_2]
15
+ populate_using_const
16
+ end
17
+
18
+ # byte array representation of the object eg [0x90, 0x40, 0x40] for NoteOn(0x40, 0x40)
19
+ def to_a
20
+ data = @data.nil? ? [] : [@data[0], @data[1]]
21
+ [(@status[0] << 4) + @status[1], *data].compact
22
+ end
23
+ alias_method :to_byte_a, :to_a
24
+ alias_method :to_byte_array, :to_a
25
+ alias_method :to_bytes, :to_a
26
+
27
+ # string representation of the object's bytes eg "904040" for NoteOn(0x40, 0x40)
28
+ def to_hex_s
29
+ TypeConversion.numeric_byte_array_to_hex_string(to_a)
30
+ end
31
+ alias_method :to_bytestr, :to_hex_s
32
+
33
+ protected
34
+
35
+ def self.included(base)
36
+ base.extend(ClassMethods)
37
+ end
38
+
39
+ def update
40
+ populate_using_const
41
+ end
42
+
43
+ private
44
+
45
+ # this will populate message metadata with information gathered from midi.yml
46
+ def populate_using_const
47
+ const_group_name = self.class.display_name
48
+ group_name_alias = self.class.constants
49
+ prop = self.class.map_constants_to
50
+ val = self.send(prop) unless prop.nil?
51
+ val ||= @status[1] # default property to use for constants
52
+ group = ConstantGroup[group_name_alias] || ConstantGroup[const_group_name]
53
+ unless group.nil?
54
+ const = group.find_by_value(val)
55
+ unless const.nil?
56
+ @const = const
57
+ @name = @const.nil? ? const.key : @const.key
58
+ @verbose_name = "#{self.class.display_name}: #{@name}"
59
+ end
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+
65
+ attr_reader :display_name, :constants, :map_constants_to
66
+
67
+ def get_constant(name)
68
+ key = @constants || @display_name
69
+ unless key.nil?
70
+ group = ConstantGroup[key]
71
+ group.find(name)
72
+ end
73
+ end
74
+
75
+ # this returns a builder for the class, preloaded with the selected const
76
+ def [](const_name)
77
+ const = get_constant(const_name)
78
+ MessageBuilder.new(self, const) unless const.nil?
79
+ end
80
+
81
+ def use_display_name(name)
82
+ @display_name = name
83
+ end
84
+
85
+ def use_constants(name, options = {})
86
+ @map_constants_to = options[:for]
87
+ @constants = name
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+
94
+ class MessageBuilder
95
+
96
+ def initialize(klass, const)
97
+ @klass = klass
98
+ @const = const
99
+ end
100
+
101
+ def new(*a)
102
+ a.last.kind_of?(Hash) ? a.last[:const] = @const : a.push(:const => @const)
103
+ @klass.new(*a)
104
+ end
105
+
106
+ end
107
+
108
+ # shortcuts for dealing with message status
109
+ module Status
110
+
111
+ # this returns the value of the Status constant with the name status_name
112
+ def self.[](status_name)
113
+ const = Constant.find("Status", status_name)
114
+ const.value unless const.nil?
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ module MIDIMessage
5
+
6
+ # MIDI System-Exclusive Messages (SysEx)
7
+ module SystemExclusive
8
+
9
+ # basic SysEx data that a message class will contain
10
+ module Base
11
+
12
+ attr_accessor :node
13
+ attr_reader :address,
14
+ :checksum
15
+
16
+ StartByte = 0xF0
17
+ EndByte = 0xF7
18
+
19
+ # an array of message parts. multiple byte parts will be represented as an array of bytes
20
+ def to_a
21
+ # this may need to be cached when properties are updated
22
+ # might be worth benchmarking
23
+ [
24
+ self.class::StartByte,
25
+ @node.manufacturer_id,
26
+ @node.device_id, # (@device_id || @node.device_id) ?? dunno
27
+ @node.model_id,
28
+ type_byte,
29
+ [address].flatten,
30
+ [value].flatten,
31
+ checksum,
32
+ self.class::EndByte
33
+ ]
34
+ end
35
+
36
+ # a flat array of message bytes
37
+ def to_numeric_byte_array
38
+ to_a.flatten
39
+ end
40
+ alias_method :to_numeric_bytes, :to_numeric_byte_array
41
+ alias_method :to_byte_array, :to_numeric_byte_array
42
+ alias_method :to_bytes, :to_numeric_byte_array
43
+ alias_method :to_byte_a, :to_numeric_byte_array
44
+
45
+ # string representation of the object's bytes
46
+ def to_hex_s
47
+ to_bytes.map { |b| s = b.to_s(16); s.length.eql?(1) ? "0#{s}" : s }.join.upcase
48
+ end
49
+ alias_method :to_bytestr, :to_hex_s
50
+
51
+ def name
52
+ "System Exclusive"
53
+ end
54
+ alias_method :verbose_name, :name
55
+
56
+ def type_byte
57
+ self.class::TypeByte
58
+ end
59
+
60
+ # alternate method from
61
+ # http://www.2writers.com/eddie/TutSysEx.htm
62
+ def checksum
63
+ sum = (address + [value].flatten).inject { |a, b| a + b }
64
+ (128 - sum.divmod(128)[1])
65
+ end
66
+
67
+ private
68
+
69
+ def initialize_sysex(address, options = {})
70
+ @node = options[:node]
71
+ @checksum = options[:checksum]
72
+ @address = address
73
+ end
74
+
75
+ end
76
+
77
+ # A SysEx command message
78
+ # a command message is identified by having a status byte equal to 0x12
79
+ #
80
+ class Command
81
+
82
+ include Base
83
+
84
+ attr_reader :data
85
+ alias_method :value, :data
86
+ #alias_method :value=, :data=
87
+
88
+ TypeByte = 0x12
89
+
90
+ def initialize(address, data, options = {})
91
+ # store as a byte if it's a single byte
92
+ @data = (data.kind_of?(Array) && data.length.eql?(1)) ? data[0] : data
93
+ initialize_sysex(address, options)
94
+ end
95
+
96
+ end
97
+
98
+ # A SysEx request message
99
+ # A request message is identified by having a status byte equal to 0x11
100
+ #
101
+ class Request
102
+
103
+ include Base
104
+
105
+ attr_reader :size
106
+
107
+ alias_method :value, :size
108
+
109
+ TypeByte = 0x11
110
+
111
+ def initialize(address, size, options = {})
112
+ self.size = (size.kind_of?(Array) && size.length.eql?(1)) ? size[0] : size
113
+ initialize_sysex(address, options)
114
+ end
115
+
116
+ def size=(val)
117
+ # accepts a Numeric or Array but
118
+ # must always store value as an array of three bytes
119
+ size = []
120
+ if val.kind_of?(Array) && val.size <= 3
121
+ size = val
122
+ elsif val.kind_of?(Numeric) && (((val + 1) / 247) <= 2)
123
+ size = []
124
+ div, mod = *val.divmod(247)
125
+ size << mod unless mod.zero?
126
+ div.times { size << 247 }
127
+ end
128
+ (3 - size.size).times { size.unshift 0 }
129
+ @size = size
130
+ end
131
+
132
+ end
133
+
134
+ #
135
+ # The SystemExclusive::Node represents a destination for a message. For example a hardware
136
+ # synthesizer or sampler
137
+ #
138
+ class Node
139
+
140
+ attr_accessor :device_id
141
+ attr_reader :manufacturer_id, :model_id
142
+
143
+ def initialize(manufacturer, options = {})
144
+ @device_id = options[:device_id]
145
+ @model_id = options[:model_id]
146
+ @manufacturer_id = manufacturer.kind_of?(Numeric) ? manufacturer : Constant.find("Manufacturer", manufacturer).value
147
+ end
148
+
149
+ # this message takes a prototype message, copies it, and returns the copy with its node set
150
+ # to this node
151
+ def new_message_from(prototype_message)
152
+ copy = prototype_message.clone
153
+ copy.node = self
154
+ copy
155
+ end
156
+
157
+ # create a new Command message associated with this node
158
+ def command(*a)
159
+ command = Command.new(*a)
160
+ command.node = self
161
+ command
162
+ end
163
+
164
+ # create a new Request message associated with this node
165
+ def request(*a)
166
+ request = Request.new(*a)
167
+ request.node = self
168
+ request
169
+ end
170
+
171
+ end
172
+
173
+ # convert raw MIDI data to SysEx message objects
174
+ def self.new(*bytes)
175
+
176
+ start_status = bytes.shift
177
+ end_status = bytes.pop
178
+
179
+ return nil unless start_status.eql?(0xF0) && end_status.eql?(0xF7)
180
+
181
+ fixed_length_message_part = bytes.slice!(0,7)
182
+
183
+ manufacturer_id = fixed_length_message_part[0]
184
+ device_id = fixed_length_message_part[1]
185
+ model_id = fixed_length_message_part[2]
186
+
187
+ msg_class = case fixed_length_message_part[3]
188
+ when 0x11 then Request
189
+ when 0x12 then Command
190
+ end
191
+
192
+ address = fixed_length_message_part.slice(4,3)
193
+ checksum = bytes.slice!((bytes.length - 1), 1)
194
+ value = bytes
195
+
196
+ node = Node.new(manufacturer_id, :model_id => model_id, :device_id => device_id)
197
+ msg_class.new(address, value, :checksum => checksum, :node => node)
198
+ end
199
+
200
+ end
201
+
202
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ module MIDIMessage
5
+
6
+ #
7
+ # MIDI System-Common message
8
+ #
9
+ class SystemCommon
10
+
11
+ include ShortMessage
12
+ use_display_name 'System Common'
13
+
14
+ attr_reader :data
15
+
16
+ def initialize(*a)
17
+ options = a.last.kind_of?(Hash) ? a.pop : {}
18
+ @const = options[:const]
19
+ @data = [a[1], a[2]]
20
+ second_nibble = @const.nil? ? a[0] : @const.value
21
+ initialize_short_message(0xF, second_nibble)
22
+ end
23
+
24
+ end
25
+
26
+ #
27
+ # MIDI System-Realtime message
28
+ #
29
+ class SystemRealtime
30
+
31
+ include ShortMessage
32
+ use_display_name 'System Realtime'
33
+
34
+ def initialize(*a)
35
+ options = a.last.kind_of?(Hash) ? a.pop : {}
36
+ @const = options[:const]
37
+ id = @const.nil? ? a[0] : @const.value
38
+ initialize_short_message(0xF, id)
39
+ end
40
+
41
+ def id
42
+ @status[1]
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MIDIMessage
4
+
5
+ # this is a helper for converting nibbles and bytes
6
+ module TypeConversion
7
+
8
+ def self.hex_chars_to_numeric_byte_array(nibbles)
9
+ nibbles = nibbles.dup
10
+ # get rid of last nibble if there's an odd number
11
+ # it will be processed later anyway
12
+ nibbles.slice!(nibbles.length-2, 1) if nibbles.length.odd?
13
+ bytes = []
14
+ while !(nibs = nibbles.slice!(0,2)).empty?
15
+ byte = (nibs[0].hex << 4) + nibs[1].hex
16
+ bytes << byte
17
+ end
18
+ bytes
19
+ end
20
+
21
+ # convert byte str to byte array
22
+ def self.hex_string_to_numeric_byte_array(str)
23
+ str = str.dup
24
+ bytes = []
25
+ until str.eql?("")
26
+ bytes << str.slice!(0, 2).hex
27
+ end
28
+ bytes
29
+ end
30
+
31
+ # converts a string of hex digits to bytes
32
+ def self.hex_str_to_hex_chars(str)
33
+ str.split(//)
34
+ end
35
+
36
+ def self.numeric_byte_array_to_hex_string(bytes)
37
+ bytes.map { |b| s = b.to_s(16); s.length.eql?(1) ? "0#{s}" : s }.join.upcase
38
+ end
39
+
40
+ def self.numeric_byte_to_hex_chars(num)
41
+ [((num & 0xF0) >> 4), (num & 0x0F)].map { |n| n.to_s(16) }
42
+ end
43
+
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # MIDI Messages in Ruby
4
+ # (c)2011 Ari Russo and licensed under the Apache 2.0 License
5
+ #
6
+ module MIDIMessage
7
+
8
+ module Process
9
+ end
10
+
11
+ VERSION = "0.2.2"
12
+
13
+ end
14
+
15
+ require 'yaml'
16
+
17
+ # messages
18
+
19
+ require 'midi-message/short_message'
20
+ require 'midi-message/channel_message'
21
+ require 'midi-message/constant'
22
+ require 'midi-message/context'
23
+ require 'midi-message/note_message'
24
+ require 'midi-message/parser'
25
+ require 'midi-message/system_message'
26
+ require 'midi-message/system_exclusive'
27
+ require 'midi-message/type_conversion'
28
+
29
+ # message processors
30
+
31
+ # modules
32
+ require "midi-message/process/processor"
33
+
34
+ # classes
35
+ require "midi-message/process/filter"
36
+ require "midi-message/process/limit"
37
+ require "midi-message/process/transpose"