midi-message 0.4.4 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +16 -16
- data/lib/midi-message.rb +5 -5
- data/lib/midi-message/channel_message.rb +55 -33
- data/lib/midi-message/constant.rb +201 -45
- data/lib/midi-message/message.rb +36 -195
- data/lib/midi-message/messages.rb +211 -0
- data/lib/midi-message/system_message.rb +2 -2
- data/test/constants_test.rb +25 -30
- data/test/context_test.rb +2 -6
- data/test/helper.rb +3 -8
- data/test/{short_message_test.rb → message_test.rb} +44 -49
- data/test/mutability_test.rb +8 -13
- data/test/parser_test.rb +39 -44
- data/test/system_exclusive_test.rb +54 -59
- data/test/system_message_test.rb +21 -27
- metadata +85 -5
- data/lib/midi-message/short_message.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33e3e855ef83585302fda5a2b7f332254b089c9f
|
4
|
+
data.tar.gz: d130122274fa395bd8939da501ac735c75f9b1ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78bd93128dae08a2f2367028f9e6fd38442919777d17b7445b8a8a16e8310f39f291a904a5ebd394bac90245abdb681c28d580e20badf7ef44274219d5959a55
|
7
|
+
data.tar.gz: b96b8747c8582d448a991f37f9fe34f7b9f66af4644892bd449267e083eefb9ceb8a1b302d7d74ff4af1870bc64b868614736dd8691e887399fd918bfef8b21a
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Ruby MIDI message objects
|
|
9
9
|
* Flexible API to accommodate various sources and destinations of MIDI data
|
10
10
|
* Simple approach to System Exclusive data and devices
|
11
11
|
* [YAML dictionary of MIDI constants](https://github.com/arirusso/midi-message/blob/master/lib/midi.yml)
|
12
|
-
|
12
|
+
|
13
13
|
## Install
|
14
14
|
|
15
15
|
`gem install midi-message`
|
@@ -23,16 +23,16 @@ Or if you're using Bundler, add this to your Gemfile
|
|
23
23
|
```ruby
|
24
24
|
require "midi-message"
|
25
25
|
```
|
26
|
-
|
26
|
+
|
27
27
|
#### Basic Messages
|
28
28
|
|
29
29
|
There are a few ways to create a new MIDI message. Here are some examples
|
30
|
-
|
31
|
-
```ruby
|
30
|
+
|
31
|
+
```ruby
|
32
32
|
MIDIMessage::NoteOn.new(0, 64, 64)
|
33
|
-
|
33
|
+
|
34
34
|
MIDIMessage::NoteOn["E4"].new(0, 100)
|
35
|
-
|
35
|
+
|
36
36
|
MIDIMessage.with(:channel => 0, :velocity => 100) { note_on("E4") }
|
37
37
|
```
|
38
38
|
|
@@ -52,27 +52,27 @@ Those expressions all evaluate to the same object
|
|
52
52
|
#### SysEx Messages
|
53
53
|
|
54
54
|
As with any kind of message, you can begin with raw data
|
55
|
-
|
55
|
+
|
56
56
|
```ruby
|
57
57
|
MIDIMessage::SystemExclusive.new(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
58
58
|
```
|
59
|
-
|
59
|
+
|
60
60
|
Or in a more object oriented way
|
61
61
|
|
62
62
|
```ruby
|
63
63
|
synth = SystemExclusive::Node.new(0x41, :model_id => 0x42, :device_id => 0x10)
|
64
|
-
|
64
|
+
|
65
65
|
SystemExclusive::Command.new([0x40, 0x7F, 0x00], 0x00, :node => synth)
|
66
66
|
```
|
67
67
|
|
68
68
|
A Node represents a device that you're sending a message to (eg. your Yamaha DX7 is a Node). Sysex messages can either be a Command or Request
|
69
|
-
|
69
|
+
|
70
70
|
You can use the Node to instantiate a message
|
71
71
|
|
72
72
|
```ruby
|
73
73
|
synth.command([0x40, 0x7F, 0x00], 0x00)
|
74
74
|
```
|
75
|
-
|
75
|
+
|
76
76
|
One way or another, you will wind up with a pair of objects like this
|
77
77
|
|
78
78
|
```ruby
|
@@ -86,18 +86,18 @@ One way or another, you will wind up with a pair of objects like this
|
|
86
86
|
@manufacturer_id=65,
|
87
87
|
@model_id=66>>
|
88
88
|
```
|
89
|
-
|
89
|
+
|
90
90
|
#### Parsing
|
91
91
|
|
92
92
|
The parse method will take any valid message data and return the object representation
|
93
93
|
|
94
94
|
```ruby
|
95
95
|
MIDIMessage.parse(0x90, 0x40, 0x40)
|
96
|
-
|
96
|
+
|
97
97
|
#<MIDIMessage::NoteOn:0x9c1c240 ..>
|
98
|
-
|
98
|
+
|
99
99
|
MIDIMessage.parse(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
|
100
|
-
|
100
|
+
|
101
101
|
#<MIDIMessage::SystemExclusive::Command:0x9c1e57c ..>
|
102
102
|
```
|
103
103
|
|
@@ -115,4 +115,4 @@ Check out [nibbler](http://github.com/arirusso/nibbler) for more advanced parsin
|
|
115
115
|
|
116
116
|
Apache 2.0, See the file LICENSE
|
117
117
|
|
118
|
-
Copyright (c) 2011-
|
118
|
+
Copyright (c) 2011-2015 Ari Russo
|
data/lib/midi-message.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Ruby MIDI message objects
|
3
3
|
#
|
4
|
-
# (c)2011-
|
4
|
+
# (c)2011-2015 Ari Russo
|
5
5
|
# Apache 2.0 License
|
6
6
|
#
|
7
7
|
|
@@ -10,7 +10,8 @@ require "forwardable"
|
|
10
10
|
require "yaml"
|
11
11
|
|
12
12
|
# Modules
|
13
|
-
require "midi-message/
|
13
|
+
require "midi-message/constant"
|
14
|
+
require "midi-message/message"
|
14
15
|
require "midi-message/channel_message"
|
15
16
|
require "midi-message/note_message"
|
16
17
|
require "midi-message/system_exclusive"
|
@@ -18,13 +19,12 @@ require "midi-message/system_message"
|
|
18
19
|
require "midi-message/type_conversion"
|
19
20
|
|
20
21
|
# Classes
|
21
|
-
require "midi-message/constant"
|
22
22
|
require "midi-message/context"
|
23
|
-
require "midi-message/
|
23
|
+
require "midi-message/messages"
|
24
24
|
require "midi-message/parser"
|
25
25
|
|
26
26
|
module MIDIMessage
|
27
27
|
|
28
|
-
VERSION = "0.4.
|
28
|
+
VERSION = "0.4.5"
|
29
29
|
|
30
30
|
end
|
@@ -4,6 +4,7 @@ module MIDIMessage
|
|
4
4
|
module ChannelMessage
|
5
5
|
|
6
6
|
include MIDIMessage # this enables ..kind_of?(MIDIMessage)
|
7
|
+
|
7
8
|
attr_reader :data, :name
|
8
9
|
|
9
10
|
# Shortcut to RawChannelMessage.new
|
@@ -19,44 +20,71 @@ module MIDIMessage
|
|
19
20
|
def initialize(*data)
|
20
21
|
data = data.dup
|
21
22
|
options = data.last.kind_of?(Hash) ? data.pop : {}
|
22
|
-
|
23
|
-
initialize_channel_message(self.class.type_for_status, *
|
23
|
+
add_constant_value(options[:const], data) unless options[:const].nil?
|
24
|
+
initialize_channel_message(self.class.type_for_status, *data)
|
24
25
|
end
|
25
26
|
|
27
|
+
# Decorates the object with the particular properties for its type
|
28
|
+
# @return [Boolean]
|
26
29
|
def initialize_properties
|
27
|
-
props = [
|
28
|
-
{ :name => :status, :index => 1 },
|
29
|
-
{ :name => :data, :index => 0 },
|
30
|
-
{ :name => :data, :index => 1 }
|
31
|
-
]
|
32
30
|
properties = self.class.properties
|
33
|
-
unless properties.nil?
|
34
|
-
properties.each_with_index do |prop,i|
|
35
|
-
self.class.send(:attr_reader, prop)
|
36
|
-
self.class.send(:define_method, "#{prop}=") do |val|
|
37
|
-
send(:instance_variable_set, "@#{prop.to_s}", val)
|
38
|
-
send(props[i][:name])[props[i][:index]] = val
|
39
|
-
update
|
40
|
-
return self
|
41
|
-
end
|
42
|
-
instance_variable_set("@#{prop}", send(props[i][:name])[props[i][:index]])
|
43
|
-
end
|
44
|
-
end
|
31
|
+
add_properties(properties) unless properties.nil?
|
45
32
|
end
|
46
33
|
|
47
|
-
|
34
|
+
private
|
48
35
|
|
49
36
|
def self.included(base)
|
50
|
-
base.send(:include,
|
37
|
+
base.send(:include, ::MIDIMessage::Message)
|
51
38
|
base.send(:extend, ClassMethods)
|
52
39
|
end
|
53
40
|
|
54
|
-
|
41
|
+
def add_constant_value(constant, data)
|
42
|
+
index = Constant::Loader.get_index(self)
|
43
|
+
data.insert(index, constant.value)
|
44
|
+
end
|
55
45
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
46
|
+
# @param [Array<Symbol>] properties
|
47
|
+
# @return [Boolean]
|
48
|
+
def add_properties(properties)
|
49
|
+
has_properties = false
|
50
|
+
schema = [
|
51
|
+
{ :name => :status, :index => 1 }, # second status nibble
|
52
|
+
{ :name => :data, :index => 0 }, # first data byte
|
53
|
+
{ :name => :data, :index => 1 } # second data byte
|
54
|
+
]
|
55
|
+
properties.each_with_index do |property, i|
|
56
|
+
property_schema = schema[i]
|
57
|
+
container = send(property_schema[:name])
|
58
|
+
index = property_schema[:index]
|
59
|
+
define_getter(property, container, index)
|
60
|
+
define_setter(property, container, index)
|
61
|
+
has_properties = true
|
62
|
+
end
|
63
|
+
has_properties
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Symbol, String] property
|
67
|
+
# @param [Hash] container
|
68
|
+
# @param [Fixnum] index
|
69
|
+
# @return [Boolean]
|
70
|
+
def define_getter(property, container, index)
|
71
|
+
self.class.send(:attr_reader, property)
|
72
|
+
instance_variable_set("@#{property.to_s}", container[index])
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param [Symbol, String] property
|
77
|
+
# @param [Hash] container
|
78
|
+
# @param [Fixnum] index
|
79
|
+
# @return [Boolean]
|
80
|
+
def define_setter(property, container, index)
|
81
|
+
self.class.send(:define_method, "#{property.to_s}=") do |value|
|
82
|
+
send(:instance_variable_set, "@#{property.to_s}", value)
|
83
|
+
container[index] = value
|
84
|
+
update
|
85
|
+
return self
|
86
|
+
end
|
87
|
+
true
|
60
88
|
end
|
61
89
|
|
62
90
|
def initialize_channel_message(status_nibble_1, status_nibble_2, data_byte_1, data_byte_2 = 0)
|
@@ -64,18 +92,12 @@ module MIDIMessage
|
|
64
92
|
@data = [data_byte_1]
|
65
93
|
@data[1] = data_byte_2 if self.class.second_data_byte?
|
66
94
|
initialize_properties
|
67
|
-
|
95
|
+
initialize_message(status_nibble_1, status_nibble_2)
|
68
96
|
end
|
69
97
|
|
70
98
|
# For defining Channel Message class types
|
71
99
|
module ClassMethods
|
72
100
|
|
73
|
-
# Get the status nibble for this particular message type
|
74
|
-
# @return [Fixnum] The status nibble
|
75
|
-
def type_for_status
|
76
|
-
Status[display_name]
|
77
|
-
end
|
78
|
-
|
79
101
|
def properties
|
80
102
|
const_get("DATA") if const_defined?("DATA")
|
81
103
|
end
|
@@ -1,58 +1,214 @@
|
|
1
1
|
module MIDIMessage
|
2
2
|
|
3
|
-
# MIDI
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@constants = constants.map { |k, v| Constant.new(k, v) }
|
11
|
-
end
|
12
|
-
|
13
|
-
def find(name)
|
14
|
-
@constants.find { |const| const.key.to_s.downcase == name.to_s.downcase }
|
15
|
-
end
|
16
|
-
alias_method :[], :find
|
17
|
-
|
18
|
-
def find_by_value(value)
|
19
|
-
@constants.find { |const| const.value.to_s.downcase == value.to_s.downcase }
|
3
|
+
# Refer to a MIDI message by its usage
|
4
|
+
# eg *C4* for MIDI note *60* or *Bank Select* for MIDI control change *0*
|
5
|
+
module Constant
|
6
|
+
|
7
|
+
def self.find(group_name, const_name)
|
8
|
+
group = Group[group_name]
|
9
|
+
group.find(const_name)
|
20
10
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
11
|
+
|
12
|
+
# MIDI Constant container
|
13
|
+
class Group
|
14
|
+
|
15
|
+
attr_reader :key, :value
|
16
|
+
|
17
|
+
def initialize(key, constants)
|
18
|
+
@key = key
|
19
|
+
@constants = constants.map { |k, v| Constant::Map.new(k, v) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Find a constant by its name
|
23
|
+
# @param [String, Symbol] name
|
24
|
+
# @return [Constant::Map]
|
25
|
+
def find(name)
|
26
|
+
@constants.find { |const| const.key.to_s.downcase == name.to_s.downcase }
|
27
|
+
end
|
28
|
+
alias_method :[], :find
|
29
|
+
|
30
|
+
# Find a constant by its value
|
31
|
+
# @param [Object] value
|
32
|
+
# @return [Constant::Map]
|
33
|
+
def find_by_value(value)
|
34
|
+
@constants.find { |const| const.value.to_s.downcase == value.to_s.downcase }
|
35
|
+
end
|
36
|
+
|
37
|
+
# All constant groups
|
38
|
+
# @return [Array<ConstantGroup>]
|
39
|
+
def self.all
|
40
|
+
ensure_initialized
|
41
|
+
@groups
|
42
|
+
end
|
43
|
+
|
44
|
+
# Find a constant group by its key
|
45
|
+
# @param [String, Symbol] key
|
46
|
+
# @return [ConstantGroup]
|
47
|
+
def self.[](key)
|
48
|
+
ensure_initialized
|
49
|
+
@groups.find { |g| g.key.to_s.downcase == key.to_s.downcase }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Lazy initialize
|
55
|
+
# @return [Boolean]
|
56
|
+
def self.ensure_initialized
|
57
|
+
populate_dictionary | populate_groups
|
58
|
+
end
|
59
|
+
|
60
|
+
# Populate the dictionary of constants
|
61
|
+
# @return [Boolean]
|
62
|
+
def self.populate_dictionary
|
63
|
+
if @dict.nil?
|
64
|
+
@dict = YAML.load_file(File.expand_path('../../midi.yml', __FILE__))
|
65
|
+
true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Populate the constant groups using the dictionary
|
70
|
+
# @return [Boolean]
|
71
|
+
def self.populate_groups
|
72
|
+
if @groups.nil? && !@dict.nil?
|
73
|
+
@groups = @dict.map { |k, v| new(k, v) }
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
25
78
|
end
|
26
79
|
|
27
|
-
|
28
|
-
|
29
|
-
|
80
|
+
# The mapping of a constant key to its value eg "Note On" => 0x9
|
81
|
+
class Map
|
82
|
+
|
83
|
+
attr_reader :key, :value
|
84
|
+
|
85
|
+
# @param [String] key
|
86
|
+
# @param [Object] value
|
87
|
+
def initialize(key, value)
|
88
|
+
@key = key
|
89
|
+
@value = value
|
90
|
+
end
|
91
|
+
|
30
92
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
93
|
+
|
94
|
+
class MessageBuilder
|
95
|
+
|
96
|
+
# @param [MIDIMessage] klass The message class to build
|
97
|
+
# @param [String] const The constant to build the message with
|
98
|
+
def initialize(klass, const)
|
99
|
+
@klass = klass
|
100
|
+
@const = const
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param [*Object] args
|
104
|
+
# @return [Message]
|
105
|
+
def new(*args)
|
106
|
+
args = args.dup
|
107
|
+
args.last.kind_of?(Hash) ? args.last[:const] = @const : args.push(:const => @const)
|
108
|
+
@klass.new(*args)
|
109
|
+
end
|
110
|
+
|
38
111
|
end
|
39
112
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
113
|
+
# Shortcuts for dealing with message status
|
114
|
+
module Status
|
115
|
+
|
116
|
+
# The value of the Status constant with the name status_name
|
117
|
+
# @param [String] status_name The key to use to look up a constant value
|
118
|
+
# @return [String] The constant value that was looked up
|
119
|
+
def self.[](status_name)
|
120
|
+
const = Constant.find("Status", status_name)
|
121
|
+
const.value unless const.nil?
|
122
|
+
end
|
123
|
+
|
49
124
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
125
|
+
|
126
|
+
# Loading constants from the spec file into messages
|
127
|
+
module Loader
|
128
|
+
|
129
|
+
extend self
|
130
|
+
|
131
|
+
# Get the index of the constant from the given message's type
|
132
|
+
# @param [Message] message
|
133
|
+
# @return [Fixnum]
|
134
|
+
def get_index(message)
|
135
|
+
key = message.class.constant_property
|
136
|
+
message.class.properties.index(key) || 0
|
137
|
+
end
|
138
|
+
|
139
|
+
# Used to populate message metadata with information gathered from midi.yml
|
140
|
+
# @param [Message] message
|
141
|
+
# @return [Hash, nil]
|
142
|
+
def get_info(message)
|
143
|
+
const_group_name = message.class.display_name
|
144
|
+
group_name_alias = message.class.constant_name
|
145
|
+
property = message.class.constant_property
|
146
|
+
value = message.send(property) unless property.nil?
|
147
|
+
value ||= message.status[1] # default property to use for constants
|
148
|
+
group = Constant::Group[group_name_alias] || Constant::Group[const_group_name]
|
149
|
+
unless group.nil?
|
150
|
+
unless (const = group.find_by_value(value)).nil?
|
151
|
+
{
|
152
|
+
:const => const,
|
153
|
+
:name => const.key,
|
154
|
+
:verbose_name => "#{message.class.display_name}: #{@name}"
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# DSL type class methods for loading constants into messages
|
161
|
+
module DSL
|
162
|
+
|
163
|
+
# Find a constant value in this class's group for the passed in key
|
164
|
+
# @param [String] name The constant key
|
165
|
+
# @return [String] The constant value
|
166
|
+
def get_constant(name)
|
167
|
+
key = constant_name || display_name
|
168
|
+
unless key.nil?
|
169
|
+
group = Group[key]
|
170
|
+
group.find(name)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# @return [String]
|
175
|
+
def display_name
|
176
|
+
const_get("DISPLAY_NAME") if const_defined?("DISPLAY_NAME")
|
177
|
+
end
|
178
|
+
|
179
|
+
# @return [Hash]
|
180
|
+
def constant_map
|
181
|
+
const_get("CONSTANT") if const_defined?("CONSTANT")
|
182
|
+
end
|
183
|
+
|
184
|
+
# @return [String]
|
185
|
+
def constant_name
|
186
|
+
constant_map.keys.first unless constant_map.nil?
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Symbol]
|
190
|
+
def constant_property
|
191
|
+
constant_map[constant_name] unless constant_map.nil?
|
192
|
+
end
|
193
|
+
|
194
|
+
# Get the status nibble for this particular message type
|
195
|
+
# @return [Fixnum] The status nibble
|
196
|
+
def type_for_status
|
197
|
+
Constant::Status[display_name]
|
198
|
+
end
|
199
|
+
|
200
|
+
# This returns a MessageBuilder for the class, preloaded with the selected const
|
201
|
+
# @param [String, Symbol] const_name The constant key to use to build the message
|
202
|
+
# @return [MIDIMessage::MessageBuilder] A MessageBuilder object for the passed in constant
|
203
|
+
def [](const_name)
|
204
|
+
const = get_constant(const_name.to_s)
|
205
|
+
MessageBuilder.new(self, const) unless const.nil?
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
54
210
|
end
|
55
|
-
|
211
|
+
|
56
212
|
end
|
57
213
|
|
58
214
|
end
|