midi-message 0.2.2

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.
@@ -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"