midi-message 0.4.4 → 0.4.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e474d330bc417e19471d1dbd4c931748445db639
4
- data.tar.gz: 8516f6b1ee25c485bfbb7265d2e4e5620f7800ed
3
+ metadata.gz: 33e3e855ef83585302fda5a2b7f332254b089c9f
4
+ data.tar.gz: d130122274fa395bd8939da501ac735c75f9b1ee
5
5
  SHA512:
6
- metadata.gz: 28b952c964be9603b65def9802d72fdf7a6c9d5b8641b537601b4b91be8c5889a7aec75cc3d396cecb25d98f62dfcbf1544d657f4a24efa61d012778bb96d46e
7
- data.tar.gz: 61d515a21f72a2d7fd340d82067fe5401bb1ad142e940b94c6fa9e1fc9114b8bc43e0331558243812cf8334135f2676fa06a962217cb44a1927af911d7a55cde
6
+ metadata.gz: 78bd93128dae08a2f2367028f9e6fd38442919777d17b7445b8a8a16e8310f39f291a904a5ebd394bac90245abdb681c28d580e20badf7ef44274219d5959a55
7
+ data.tar.gz: b96b8747c8582d448a991f37f9fe34f7b9f66af4644892bd449267e083eefb9ceb8a1b302d7d74ff4af1870bc64b868614736dd8691e887399fd918bfef8b21a
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2011-2014 Ari Russo
1
+ Copyright 2011-2015 Ari Russo
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
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-2014 Ari Russo
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-2014 Ari Russo
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/short_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/message"
23
+ require "midi-message/messages"
24
24
  require "midi-message/parser"
25
25
 
26
26
  module MIDIMessage
27
27
 
28
- VERSION = "0.4.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
- processed_data = options[:const].nil? ? data : data_with_const(data, options[:const])
23
- initialize_channel_message(self.class.type_for_status, *processed_data)
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
- protected
34
+ private
48
35
 
49
36
  def self.included(base)
50
- base.send(:include, ShortMessage)
37
+ base.send(:include, ::MIDIMessage::Message)
51
38
  base.send(:extend, ClassMethods)
52
39
  end
53
40
 
54
- private
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
- def data_with_const(data, const)
57
- key = self.class.constant_property
58
- ind = self.class.properties.index(key) || 0
59
- data.insert(ind, const.value)
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
- initialize_short_message(status_nibble_1, status_nibble_2)
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 Constants
4
- class ConstantGroup
5
-
6
- attr_reader :key, :value
7
-
8
- def initialize(key, constants)
9
- @key = key
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
- def self.all
23
- ensure_initialized
24
- @groups
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
- def self.[](key)
28
- ensure_initialized
29
- @groups.find { |g| g.key.to_s.downcase == key.to_s.downcase }
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
- private
33
-
34
- # lazy initialize
35
- def self.ensure_initialized
36
- @dict ||= YAML.load_file(File.expand_path('../../midi.yml', __FILE__))
37
- @groups ||= @dict.map { |k, v| new(k, v) }
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
- end
41
-
42
- class Constant
43
-
44
- attr_reader :key, :value
45
-
46
- def initialize(key, value)
47
- @key = key
48
- @value = value
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
- def self.find(group_name, const_name)
52
- group = ConstantGroup[group_name]
53
- group.find(const_name)
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