midi-events 0.6.1 → 0.7.0

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.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # MIDI Events
2
2
 
3
+ [![Ruby Version](https://img.shields.io/badge/ruby-2.7+-red.svg)](https://www.ruby-lang.org/)
4
+ [![License](https://img.shields.io/badge/license-LGPL--3.0--or--later-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0.html)
5
+
3
6
  **Ruby MIDI Events Objects**
4
7
 
5
8
  This library is part of a suite of Ruby libraries for MIDI:
@@ -61,6 +64,49 @@ Those expressions all evaluate to the same object:
61
64
  @verbose_name="Note On: E4">
62
65
  ```
63
66
 
67
+ #### Raw Channel Messages
68
+
69
+ You can also create raw channel messages directly from nibbles and bytes:
70
+
71
+ ```ruby
72
+ MIDIEvents::ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
73
+ ```
74
+
75
+ #### Mutable Properties
76
+
77
+ Some message properties can be modified after creation:
78
+
79
+ ```ruby
80
+ msg = MIDIEvents::NoteOn["E4"].new(0, 100)
81
+ msg.note += 5 # Transpose up 5 semitones
82
+ ```
83
+
84
+ #### System Realtime Messages
85
+
86
+ System Realtime messages are used for synchronization:
87
+
88
+ ```ruby
89
+ MIDIEvents::SystemRealtime["Start"].new
90
+ MIDIEvents::SystemRealtime["Stop"].new
91
+ ```
92
+
93
+ #### Building Melodies
94
+
95
+ You can construct sequences of notes programmatically:
96
+
97
+ ```ruby
98
+ channel = 0
99
+ notes = [36, 40, 43] # C E G
100
+ octaves = 2
101
+ velocity = 100
102
+
103
+ melody = []
104
+
105
+ (0..((octaves-1)*12)).step(12) do |oct|
106
+ notes.each { |note| melody << MIDIEvents::NoteOn.new(channel, note + oct, velocity) }
107
+ end
108
+ ```
109
+
64
110
  #### SysEx Messages
65
111
 
66
112
  As with any kind of message, you can begin with raw data:
@@ -101,16 +147,15 @@ One way or another, you will wind up with a pair of objects like this:
101
147
 
102
148
  ## Documentation
103
149
 
104
- * (**TO DO**) [rdoc](http://rubydoc.info/github/javier-sy/midi-events)
150
+ * [rdoc](http://rubydoc.info/github/javier-sy/midi-events)
105
151
 
106
152
  ## Differences between [MIDI Events](https://github.com/javier-sy/midi-events) library and [MIDI Message](https://github.com/arirusso/midi-message) library
107
153
 
108
154
  [MIDI Events](https://github.com/javier-sy/midi-events) is mostly a clone of [MIDI Message](https://github.com/arirusso/midi-message) with some modifications:
155
+
109
156
  * Renamed gem to midi-events instead of midi-message
110
157
  * Renamed module to MIDIEvents instead of MIDIMessage
111
158
  * Removed parsing features (in favour of the more complete parser [MIDI Parser](https://github.com/javier-sy/midi-parser))
112
- * TODO: update tests to use rspec instead of rake
113
- * TODO: migrate to (or confirm it's working ok on) Ruby 3.0 and Ruby 3.1
114
159
 
115
160
  ## Then, why does exist this library if it is mostly a clone of another library?
116
161
 
@@ -144,16 +189,6 @@ I've decided to publish my own renamed version of the modified dependencies beca
144
189
 
145
190
  All in all I have decided to publish a suite of libraries optimized for MusaDSL use case that also can be used by other people in their projects.
146
191
 
147
- | Function | Library | Based on Ari Russo's| Difference |
148
- | --- | --- | --- | --- |
149
- | MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) | [MIDI Message](https://github.com/arirusso/midi-message) | removed parsing, small improvements |
150
- | MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) | [Nibbler](https://github.com/arirusso/nibbler) | removed process history information, minor optimizations |
151
- | MIDI communication with Instruments and Control Surfaces | [MIDI Communications](https://github.com/javier-sy/midi-communications) | [unimidi](https://github.com/arirusso/unimidi) | use of [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos, removed process history information, removed buffering, removed command line script)
152
- | Low level MIDI interface to MacOS | [MIDI Communications MacOS Layer](https://github.com/javier-sy/midi-communications-macos) | [ffi-coremidi](https://github.com/arirusso/ffi-coremidi) | removed buffering and process history information, locking behaviour when waiting midi events, improved midi devices name detection, minor optimizations |
153
- | Low level MIDI interface to Linux | **TO DO** | | |
154
- | Low level MIDI interface to JRuby | **TO DO** | | |
155
- | Low level MIDI interface to Windows | **TO DO** | | |
156
-
157
192
  ## Author
158
193
 
159
194
  * [Javier Sánchez Yeste](https://github.com/javier-sy)
@@ -164,7 +199,7 @@ Thanks to [Ari Russo](http://github.com/arirusso) for his ruby library [MIDI Mes
164
199
 
165
200
  ## License
166
201
 
167
- [MIDI Events](https://github.com/javier-sy/midi-events) Copyright (c) 2021 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
202
+ [MIDI Events](https://github.com/javier-sy/midi-events) Copyright (c) 2021-2025 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
168
203
 
169
204
  [MIDI Message](https://github.com/arirusso/midi-message) Copyright (c) 2011-2015 [Ari Russo](http://arirusso.com), licensed under Apache License 2.0 (see the file LICENSE.midi-message)
170
205
 
@@ -9,19 +9,12 @@ require "midi-events"
9
9
 
10
10
  # Here are examples of different ways to construct messages, going from low to high-level
11
11
 
12
- pp MIDIEvents.parse(0x90, 0x40, 0x40)
13
-
14
12
  channel_msg = MIDIEvents::ChannelMessage.new(0x9, 0x0, 0x40, 0x40)
15
13
 
16
14
  pp channel_msg
17
15
 
18
- # this will return a NoteOn object with the properties of channel_msg
19
- pp channel_msg.to_type
20
-
21
16
  pp MIDIEvents::ChannelMessage.new(MIDIEvents::Constant::Status["Note On"], 0x0, 0x40, 0x40)
22
17
 
23
- pp MIDIEvents::ChannelMessage.new(MIDIEvents::Constant::Status["Note On"], 0x0, 0x40, 0x40).to_type
24
-
25
18
  pp MIDIEvents::NoteOn.new(0, 64, 64) # or NoteOn.new(0x0, 0x64, 0x64)
26
19
 
27
20
  # some message properties are mutable
@@ -1,6 +1,26 @@
1
1
  module MIDIEvents
2
- # Refer to a MIDI message by its usage
3
- # eg *C4* for MIDI note *60* or *Bank Select* for MIDI control change *0*
2
+ # MIDI constant lookups and mappings
3
+ #
4
+ # Provides a flexible system for referring to MIDI messages by their human-readable names
5
+ # instead of numeric values. For example, "C4" for MIDI note 60, or "Bank Select" for
6
+ # MIDI control change 0.
7
+ #
8
+ # Constants are loaded from a YAML dictionary (midi.yml) and organized into groups
9
+ # (Notes, Control Changes, Status bytes, etc.).
10
+ #
11
+ # @example Looking up note values
12
+ # MIDIEvents::Constant.find('Note', 'C4')
13
+ # # => #<MIDIEvents::Constant::Map @key="C4", @value=60>
14
+ #
15
+ # @example Getting constant value directly
16
+ # MIDIEvents::Constant.value('Note', 'E4')
17
+ # # => 64
18
+ #
19
+ # @example Using constants in message creation
20
+ # MIDIEvents::NoteOn["C4"].new(0, 100)
21
+ # # Creates a NoteOn message for C4 (MIDI note 60)
22
+ #
23
+ # @api public
4
24
  module Constant
5
25
  # Get a Mapping object for the specified constant
6
26
  # @param [Symbol, String] group_name
@@ -20,46 +40,88 @@ module MIDIEvents
20
40
  map.value
21
41
  end
22
42
 
43
+ # Name manipulation utilities for constant lookups
44
+ #
45
+ # Provides methods to normalize and compare constant names in a case-insensitive
46
+ # manner, supporting both "Control Change" and "control_change" formats.
47
+ #
48
+ # @api private
23
49
  module Name
24
50
  extend self
25
51
 
26
- # eg "Control Change" -> "control_change"
27
- # @param [Symbol, String] string
28
- # @return [String]
52
+ # Convert a name to underscore format
53
+ #
54
+ # @example
55
+ # MIDIEvents::Constant::Name.underscore("Control Change")
56
+ # # => "control_change"
57
+ #
58
+ # @param [Symbol, String] string The name to convert
59
+ # @return [String] The underscored version
29
60
  def underscore(string)
30
61
  string.to_s.downcase.gsub(/(\ )+/, '_')
31
62
  end
32
63
 
33
- # @param [Symbol, String] key
34
- # @param [Symbol, String] other
35
- # @return [Boolean]
64
+ # Check if two names match (case-insensitive, supports underscored or spaced)
65
+ #
66
+ # @example
67
+ # MIDIEvents::Constant::Name.match?("Control Change", "control_change")
68
+ # # => true
69
+ #
70
+ # @param [Symbol, String] key First name to compare
71
+ # @param [Symbol, String] other Second name to compare
72
+ # @return [Boolean] True if names match
36
73
  def match?(key, other)
37
74
  match_key = key.to_s.downcase
38
75
  [match_key, Name.underscore(match_key)].include?(other.to_s.downcase)
39
76
  end
40
77
  end
41
78
 
42
- # MIDI Constant container
79
+ # Container for a group of related MIDI constants
80
+ #
81
+ # Groups organize constants by category (e.g., "Note", "Control Change", "Status").
82
+ # Each group contains multiple Map objects that pair constant names with their values.
83
+ #
84
+ # @example Accessing a constant group
85
+ # group = MIDIEvents::Constant::Group['Note']
86
+ # group.find('C4') # => Map object for C4
87
+ #
88
+ # @api public
43
89
  class Group
44
- attr_reader :constants, :key
90
+ # @return [Array<MIDIEvents::Constant::Map>] The constants in this group
91
+ attr_reader :constants
45
92
 
46
- # @param [String] key
47
- # @param [Hash] constants
93
+ # @return [String] The group's key/name
94
+ attr_reader :key
95
+
96
+ # Create a new constant group
97
+ #
98
+ # @param [String] key The group identifier
99
+ # @param [Hash] constants Hash of constant names to values
48
100
  def initialize(key, constants)
49
101
  @key = key
50
102
  @constants = constants.map { |k, v| Constant::Map.new(k, v) }
51
103
  end
52
104
 
53
- # Find a constant by its name
54
- # @param [String, Symbol] name
55
- # @return [Constant::Map]
105
+ # Find a constant in this group by its name
106
+ #
107
+ # @example
108
+ # group = MIDIEvents::Constant::Group['Note']
109
+ # group.find('E4') # => Map for E4 (value 64)
110
+ #
111
+ # @param [String, Symbol] name The constant name to find
112
+ # @return [MIDIEvents::Constant::Map, nil] The matching constant or nil
56
113
  def find(name)
57
114
  @constants.find { |const| Name.match?(const.key, name) }
58
115
  end
59
116
 
60
- # Find a constant by its value
61
- # @param [Object] value
62
- # @return [Constant::Map]
117
+ # Find a constant in this group by its value (reverse lookup)
118
+ #
119
+ # @example
120
+ # group = MIDIEvents::Constant::Group['Note']
121
+ # group.find_by_value(64) # => Map for E4
122
+ #
123
+ # @param [Object] value The numeric value to find
124
+ # @return [MIDIEvents::Constant::Map, nil] The matching constant or nil
63
125
  def find_by_value(value)
64
126
  @constants.find { |const| Name.match?(const.value, value) }
65
127
  end
@@ -113,28 +175,61 @@ module MIDIEvents
113
175
 
114
176
  end
115
177
 
116
- # The mapping of a constant key to its value eg "Note On" => 0x9
178
+ # A single constant mapping (name to value pair)
179
+ #
180
+ # Represents an individual MIDI constant, pairing a human-readable name
181
+ # with its numeric MIDI value.
182
+ #
183
+ # @example
184
+ # map = MIDIEvents::Constant::Map.new("C4", 60)
185
+ # map.key # => "C4"
186
+ # map.value # => 60
187
+ #
188
+ # @api public
117
189
  class Map
118
- attr_reader :key, :value
190
+ # @return [String] The human-readable name of the constant
191
+ attr_reader :key
192
+
193
+ # @return [Object] The numeric MIDI value
194
+ attr_reader :value
119
195
 
120
- # @param [String] key
121
- # @param [Object] value
196
+ # Create a new constant mapping
197
+ #
198
+ # @param [String] key The constant name (e.g., "C4", "Note On")
199
+ # @param [Object] value The constant value (e.g., 60, 0x9)
122
200
  def initialize(key, value)
123
201
  @key = key
124
202
  @value = value
125
203
  end
126
204
  end
127
205
 
206
+ # Helper class for building messages with pre-bound constants
207
+ #
208
+ # This class is returned when you call a message class's bracket method
209
+ # with a constant name (e.g., NoteOn["C4"]). It stores the constant
210
+ # and message class so that when you call #new, it creates the message
211
+ # with the constant value already filled in.
212
+ #
213
+ # @example
214
+ # builder = MIDIEvents::NoteOn["C4"]
215
+ # note = builder.new(0, 100) # channel 0, velocity 100
216
+ # # The note value (60 for C4) is automatically filled in
217
+ #
218
+ # @api private
128
219
  class MessageBuilder
129
- # @param [MIDIEvents] klass The message class to build
130
- # @param [MIDIEvents::Constant::Map] const The constant to build the message with
220
+ # Create a new message builder
221
+ #
222
+ # @param [Class] klass The message class to build (e.g., NoteOn)
223
+ # @param [MIDIEvents::Constant::Map] const The constant to build with
131
224
  def initialize(klass, const)
132
225
  @klass = klass
133
226
  @const = const
134
227
  end
135
228
 
136
- # @param [*Object] args
137
- # @return [Message]
229
+ # Create a message instance with the bound constant
230
+ #
231
+ # @param [Array] args The remaining arguments for the message constructor
232
+ # @return [MIDIEvents::Message] The constructed message
138
233
  def new(*args)
139
234
  args = args.dup
140
235
  args.last.is_a?(Hash) ? args.last[:const] = @const : args.push(const: @const)
@@ -142,13 +237,23 @@ module MIDIEvents
142
237
  end
143
238
  end
144
239
 
145
- # Shortcuts for dealing with message status
240
+ # Shortcuts for dealing with MIDI status bytes
241
+ #
242
+ # Provides quick lookup of status byte values by their human-readable names
243
+ # (e.g., "Note On" => 0x9).
244
+ #
245
+ # @example
246
+ # MIDIEvents::Constant::Status['Note On'] # => 0x9
247
+ # MIDIEvents::Constant::Status['Control Change'] # => 0xB
248
+ #
249
+ # @api public
146
250
  module Status
147
251
  extend self
148
252
 
149
- # The value of the Status constant with the name status_name
150
- # @param [String] status_name The key to use to look up a constant value
151
- # @return [String] The constant value that was looked up
253
+ # Find a status byte value by its name
254
+ #
255
+ # @param [String, Symbol] status_name The name of the status (e.g., "Note On")
256
+ # @return [Integer, nil] The status nibble value or nil if not found
152
257
  def find(status_name)
153
258
  const = Constant.find('Status', status_name)
154
259
  const&.value
@@ -156,21 +261,31 @@ module MIDIEvents
156
261
  alias [] find
157
262
  end
158
263
 
159
- # Loading constants from the spec file into messages
264
+ # Internal system for loading constants into message objects
265
+ #
266
+ # Handles the automatic population of message metadata (names, verbose names)
267
+ # based on constant definitions in midi.yml.
268
+ #
269
+ # @api private
160
270
  module Loader
161
271
  extend self
162
272
 
163
- # Get the index of the constant from the given message's type
164
- # @param [Message] message
165
- # @return [Fixnum]
273
+ # Get the property index for a constant in a message
274
+ #
275
+ # @param [MIDIEvents::Message] message The message to inspect
276
+ # @return [Integer] The index of the constant property
166
277
  def get_index(message)
167
278
  key = message.class.constant_property
168
279
  message.class.properties.index(key) || 0
169
280
  end
170
281
 
171
- # Used to populate message metadata with information gathered from midi.yml
172
- # @param [Message] message
173
- # @return [Hash, nil]
282
+ # Populate message metadata using constant information from midi.yml
283
+ #
284
+ # Looks up the constant for a message and returns metadata including
285
+ # the constant object, name, and verbose name.
286
+ #
287
+ # @param [MIDIEvents::Message] message The message to populate
288
+ # @return [Hash, nil] Hash with :const, :name, :verbose_name keys, or nil
174
289
  def get_info(message)
175
290
  const_group_name = message.class.display_name
176
291
  group_name_alias = message.class.constant_name
@@ -189,7 +304,12 @@ module MIDIEvents
189
304
  end
190
305
  end
191
306
 
192
- # DSL type class methods for loading constants into messages
307
+ # DSL class methods for message classes to work with constants
308
+ #
309
+ # These methods are extended into message classes to provide constant lookup
310
+ # functionality (e.g., NoteOn["C4"]).
311
+ #
312
+ # @api private
193
313
  module DSL
194
314
  # Find a constant value in this class's group for the passed in key
195
315
  # @param [String] name The constant key
@@ -228,9 +348,16 @@ module MIDIEvents
228
348
  Constant::Status[display_name]
229
349
  end
230
350
 
231
- # This returns a MessageBuilder for the class, preloaded with the selected const
232
- # @param [String, Symbol] const_name The constant key to use to build the message
233
- # @return [MIDIEvents::MessageBuilder] A MessageBuilder object for the passed in constant
351
+ # Find a constant and return a MessageBuilder bound to it
352
+ #
353
+ # This enables the bracket syntax: NoteOn["C4"].new(channel, velocity)
354
+ #
355
+ # @example
356
+ # builder = MIDIEvents::NoteOn["C4"]
357
+ # note = builder.new(0, 100) # Creates NoteOn for C4 on channel 0
358
+ #
359
+ # @param [String, Symbol] const_name The constant name to look up
360
+ # @return [MIDIEvents::Constant::MessageBuilder, nil] Builder or nil if not found
234
361
  def find(const_name)
235
362
  const = get_constant(const_name.to_s)
236
363
  MessageBuilder.new(self, const) unless const.nil?
@@ -1,15 +1,44 @@
1
1
  module MIDIEvents
2
2
 
3
- # A DSL for instantiating message objects
3
+ # DSL for creating MIDI messages with shared context
4
+ #
5
+ # Provides a convenient way to create multiple MIDI messages that share common
6
+ # parameters (like channel and velocity) without repeating them for each message.
7
+ #
8
+ # @example Basic context usage
9
+ # MIDIEvents.with(channel: 0, velocity: 100) do
10
+ # note_on("C4") # Creates NoteOn with channel 0, velocity 100
11
+ # note_off("C4") # Creates NoteOff with channel 0, velocity 100
12
+ # end
13
+ #
14
+ # @example Override context parameters
15
+ # MIDIEvents.with(channel: 0, velocity: 100) do
16
+ # note_on("C4") # Uses context: channel 0, velocity 100
17
+ # note_on("E4", velocity: 127) # Overrides velocity to 127
18
+ # end
19
+ #
20
+ # @example Control changes in context
21
+ # MIDIEvents.with(channel: 0) do
22
+ # control_change("Modulation Wheel", 64)
23
+ # program_change("Acoustic Grand Piano")
24
+ # end
25
+ #
26
+ # @api public
4
27
  class Context
5
28
 
6
- attr_accessor :channel, :velocity
29
+ # @return [Integer, nil] The MIDI channel (0-15) for messages created in this context
30
+ attr_accessor :channel
7
31
 
8
- # Open a context with the given options
9
- # @param [Hash] options
10
- # @param [Proc] block
11
- # @option options [Fixnum] :channel
12
- # @option options [Fixnum] :velocity
32
+ # @return [Integer, nil] The velocity (0-127) for note messages created in this context
33
+ attr_accessor :velocity
34
+
35
+ # Create and execute a context with the given parameters
36
+ #
37
+ # @param [Hash] options Context parameters
38
+ # @param [Proc] block The block to execute within this context
39
+ # @option options [Integer] :channel MIDI channel (0-15)
40
+ # @option options [Integer] :velocity Note velocity (0-127)
41
+ # @return [Object] The result of evaluating the block
13
42
  def self.with(options = {}, &block)
14
43
  new(options, &block).instance_eval(&block)
15
44
  end
@@ -1,15 +1,34 @@
1
1
  module MIDIEvents
2
-
3
- # Helper for converting nibbles and bytes
2
+
3
+ # Utilities for converting between different MIDI data representations
4
+ #
5
+ # Provides methods to convert between hex strings, nibbles, and numeric byte arrays.
6
+ # Useful for working with MIDI data in different formats.
7
+ #
8
+ # @example Converting hex string to bytes
9
+ # MIDIEvents::TypeConversion.hex_string_to_numeric_byte_array("904040")
10
+ # # => [0x90, 0x40, 0x40]
11
+ #
12
+ # @example Converting bytes to hex string
13
+ # MIDIEvents::TypeConversion.numeric_byte_array_to_hex_string([0x90, 0x40, 0x40])
14
+ # # => "904040"
15
+ #
16
+ # @api public
4
17
  module TypeConversion
5
-
18
+
6
19
  extend self
7
20
 
8
- # Convert an array of hex nibbles to an array of numeric bytes
9
- # eg ["9", "0", "4", "0"] to [0x90, 0x40]
21
+ # Convert an array of hex character nibbles to numeric bytes
22
+ #
23
+ # Pairs nibbles together to form bytes. If there's an odd number of nibbles,
24
+ # the second-to-last nibble is removed.
10
25
  #
11
- # @param [Array<String>] An array of hex nibbles eg ["9", "0", "4", "0"]
12
- # @return [Array<Fixnum] An array of numeric bytes eg [0x90, 0x40]
26
+ # @example
27
+ # hex_chars_to_numeric_byte_array(["9", "0", "4", "0"])
28
+ # # => [0x90, 0x40]
29
+ #
30
+ # @param [Array<String>] nibbles Array of hex character strings (e.g., ["9", "0", "4", "0"])
31
+ # @return [Array<Integer>] Array of numeric bytes (e.g., [0x90, 0x40])
13
32
  def hex_chars_to_numeric_byte_array(nibbles)
14
33
  nibbles = nibbles.dup # Don't mess with the input
15
34
  # get rid of last nibble if there's an odd number
@@ -23,10 +42,14 @@ module MIDIEvents
23
42
  bytes
24
43
  end
25
44
 
26
- # Convert byte string to an array of numeric bytes
27
- # eg. "904040" to [0x90, 0x40, 0x40]
28
- # @param [String] string A string representing hex digits eg "904040"
29
- # @return [Array<Fixnum>] An array of numeric bytes eg [0x90, 0x40, 0x40]
45
+ # Convert a hex string to an array of numeric bytes
46
+ #
47
+ # @example
48
+ # hex_string_to_numeric_byte_array("904040")
49
+ # # => [0x90, 0x40, 0x40]
50
+ #
51
+ # @param [String] string A string of hex digits (e.g., "904040")
52
+ # @return [Array<Integer>] An array of numeric bytes (e.g., [0x90, 0x40, 0x40])
30
53
  def hex_string_to_numeric_byte_array(string)
31
54
  string = string.dup
32
55
  bytes = []
@@ -36,18 +59,26 @@ module MIDIEvents
36
59
  bytes
37
60
  end
38
61
 
39
- # Convert a string of hex digits to an array of nibbles
40
- # eg. "904040" to ["9", "0", "4", "0", "4", "0"]
41
- # @param [String] string A string representing hex digits eg "904040"
42
- # @return [Array<String>] An array of hex nibble chars eg ["9", "0", "4", "0", "4", "0"]
62
+ # Convert a hex string to an array of character nibbles
63
+ #
64
+ # @example
65
+ # hex_str_to_hex_chars("904040")
66
+ # # => ["9", "0", "4", "0", "4", "0"]
67
+ #
68
+ # @param [String] string A string of hex digits (e.g., "904040")
69
+ # @return [Array<String>] An array of individual hex character nibbles
43
70
  def hex_str_to_hex_chars(string)
44
71
  string.split(//)
45
72
  end
46
73
 
47
- # Convert an array of numeric bytes to a string of hex digits
48
- # eg. [0x90, 0x40, 0x40] to "904040"
49
- # @param [Array<Fixnum>] bytes An array of numeric bytes eg [0x90, 0x40, 0x40]
50
- # @return [String] A string representing hex digits eg "904040"
74
+ # Convert an array of numeric bytes to an uppercase hex string
75
+ #
76
+ # @example
77
+ # numeric_byte_array_to_hex_string([0x90, 0x40, 0x40])
78
+ # # => "904040"
79
+ #
80
+ # @param [Array<Integer>] bytes An array of numeric bytes (e.g., [0x90, 0x40, 0x40])
81
+ # @return [String] An uppercase hex string (e.g., "904040")
51
82
  def numeric_byte_array_to_hex_string(bytes)
52
83
  string_bytes = bytes.map do |byte|
53
84
  string = byte.to_s(16)
@@ -57,19 +88,27 @@ module MIDIEvents
57
88
  string_bytes.join.upcase
58
89
  end
59
90
 
60
- # Convert a numeric byte to hex chars
61
- # eg 0x90 to ["9", "0"]
62
- # @param [Fixnum] num A numeric byte eg 0x90
63
- # @return [Array<String>] An array of hex nibble chars eg ["9", "0"]
91
+ # Convert a numeric byte to hex character nibbles
92
+ #
93
+ # @example
94
+ # numeric_byte_to_hex_chars(0x90)
95
+ # # => ["9", "0"]
96
+ #
97
+ # @param [Integer] num A numeric byte (e.g., 0x90)
98
+ # @return [Array<String>] An array of two hex character nibbles (e.g., ["9", "0"])
64
99
  def numeric_byte_to_hex_chars(num)
65
100
  nibbles = numeric_byte_to_nibbles(num)
66
101
  nibbles.map { |n| n.to_s(16) }
67
102
  end
68
103
 
69
- # Convert a numeric byte to nibbles
70
- # eg 0x90 to [0x9, 0x0]
71
- # @param [Fixnum] num A numeric byte eg 0x90
72
- # @return [Array<Fixnum>] An array of nibbles eg [0x9, 0x0]
104
+ # Split a numeric byte into its high and low nibbles
105
+ #
106
+ # @example
107
+ # numeric_byte_to_nibbles(0x90)
108
+ # # => [0x9, 0x0]
109
+ #
110
+ # @param [Integer] num A numeric byte (e.g., 0x90)
111
+ # @return [Array<Integer>] An array of two nibbles [high, low] (e.g., [0x9, 0x0])
73
112
  def numeric_byte_to_nibbles(num)
74
113
  [((num & 0xF0) >> 4), (num & 0x0F)]
75
114
  end
@@ -1,3 +1,47 @@
1
+ # Ruby MIDI Events library - object-oriented representation of MIDI messages
2
+ #
3
+ # This library provides a comprehensive set of classes and modules for working with MIDI events
4
+ # in Ruby. It offers an intuitive API for creating and manipulating various MIDI message types
5
+ # including channel messages (notes, control changes, program changes), system messages, and
6
+ # system exclusive (SysEx) messages.
7
+ #
8
+ # @example Basic note creation
9
+ # require 'midi-events'
10
+ #
11
+ # # Create a middle C note-on message on channel 0 with velocity 64
12
+ # note = MIDIEvents::NoteOn.new(0, 64, 64)
13
+ # # => #<MIDIEvents::NoteOn @channel=0, @note=64, @velocity=64>
14
+ #
15
+ # @example Using note names
16
+ # # Create note using named constant
17
+ # note = MIDIEvents::NoteOn["E4"].new(0, 100)
18
+ # # => #<MIDIEvents::NoteOn @channel=0, @note=64, @velocity=100, @name="E4">
19
+ #
20
+ # @example Using context for common parameters
21
+ # # Set channel and velocity as context
22
+ # MIDIEvents.with(channel: 0, velocity: 100) do
23
+ # note_on("E4") # Creates note-on with channel 0, velocity 100
24
+ # end
25
+ #
26
+ # @example Working with control changes
27
+ # # Create modulation wheel control change
28
+ # cc = MIDIEvents::ControlChange["Modulation Wheel"].new(0, 64)
29
+ #
30
+ # @example System exclusive messages
31
+ # # Create a SysEx node representing a device
32
+ # synth = MIDIEvents::SystemExclusive::Node.new(0x41, model_id: 0x42, device_id: 0x10)
33
+ #
34
+ # # Send a command to the device
35
+ # command = synth.command([0x40, 0x7F, 0x00], 0x00)
36
+ #
37
+ # @see MIDIEvents::Context For DSL-style message creation
38
+ # @see MIDIEvents::Constant For MIDI constant lookups
39
+ #
40
+ # @author (c)2021 Javier Sánchez Yeste for the modifications, licensed under LGPL 3.0 License
41
+ # @author (c)2011-2015 Ari Russo for original MIDI Message library, licensed under Apache 2.0 License
42
+ #
43
+ # @note This library is part of the MusaDSL ecosystem
44
+ # @note Based on Ari Russo's MIDI Message library with performance optimizations
1
45
  module MIDIEvents
2
- VERSION = '0.6.1'.freeze
46
+ VERSION = '0.7.0'.freeze
3
47
  end
data/lib/midi-events.rb CHANGED
@@ -1,10 +1,3 @@
1
- #
2
- # Ruby MIDI message objects
3
- #
4
- # (c)2021 Javier Sánchez Yeste for the modifications, licensed under LGPL 3.0 License
5
- # (c)2011-2015 Ari Russo for original MIDI Message library, licensed under Apache 2.0 License
6
- #
7
-
8
1
  # Libs
9
2
  require 'forwardable'
10
3
  require 'yaml'