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 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