midi-parser 0.4.1 → 0.5.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 Parser
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 Parser for Raw MIDI Messages**
4
7
 
5
8
  This library is part of a suite of Ruby libraries for MIDI:
@@ -92,7 +95,7 @@ Use running status:
92
95
 
93
96
  ```ruby
94
97
  midi_parser.parse(0x40, 64)
95
- => #<MIDIMEvents::NoteOn:0x98c9818 ...>
98
+ => #<MIDIEvents::NoteOn:0x98c9818 ...>
96
99
  ```
97
100
 
98
101
  Add an incomplete message:
@@ -116,10 +119,11 @@ MIDI Parser generates [midi-events](http://github.com/javier-sy/midi-events) obj
116
119
 
117
120
  ## Documentation
118
121
 
119
- * (**TO DO**) [rdoc](http://rubydoc.info/github/javier-sy/midi-parser)
122
+ * [rdoc](http://rubydoc.info/github/javier-sy/midi-parser)
120
123
 
121
124
  ## Differences between [MIDI Parser](https://github.com/javier-sy/midi-parser) and [Nibbler](https://github.com/arirusso/nibbler)
122
125
  [MIDI Parser](https://github.com/javier-sy/midi-parser) is mostly a clone of [Nibbler](https://github.com/arirusso/nibbler) with some modifications:
126
+
123
127
  * Removed logging attributes (messages, rejected, processed) to reduce parsing overhead
124
128
  * Updated dependencies versions
125
129
  * Source updated to Ruby 2.7 code conventions (method keyword parameters instead of options = {}, hash keys as 'key:' instead of ':key =>', etc.)
@@ -128,8 +132,6 @@ MIDI Parser generates [midi-events](http://github.com/javier-sy/midi-events) obj
128
132
  * Renamed module to MIDIParser instead of Nibbler
129
133
  * Renamed gem to midi-parser instead of nibbler
130
134
  * Minor docs fixing
131
- * TODO: update tests to use rspec instead of rake
132
- * TODO: migrate to (or confirm it's working ok on) Ruby 3.0 or Ruby 3.1
133
135
 
134
136
  ## Then, why does exist this library if it is mostly a clone of another library?
135
137
 
@@ -163,16 +165,6 @@ I've decided to publish my own renamed version of the modified dependencies beca
163
165
 
164
166
  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.
165
167
 
166
- | Function | Library | Based on Ari Russo's| Difference |
167
- | --- | --- | --- | --- |
168
- | MIDI Events representation | [MIDI Events](https://github.com/javier-sy/midi-events) | [MIDI Message](https://github.com/arirusso/midi-message) | removed parsing, small improvements |
169
- | MIDI Data parsing | [MIDI Parser](https://github.com/javier-sy/midi-parser) | [Nibbler](https://github.com/arirusso/nibbler) | removed process history information, minor optimizations |
170
- | 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)
171
- | 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 |
172
- | Low level MIDI interface to Linux | **TO DO** | | |
173
- | Low level MIDI interface to JRuby | **TO DO** | | |
174
- | Low level MIDI interface to Windows | **TO DO** | | |
175
-
176
168
  ## Author
177
169
 
178
170
  * [Javier Sánchez Yeste](https://github.com/javier-sy)
@@ -183,6 +175,6 @@ Thanks to [Ari Russo](http://github.com/arirusso) for his ruby library [Nibbler]
183
175
 
184
176
  ## License
185
177
 
186
- [MIDI Parser](https://github.com/javier-sy/midi-parser) Copyright (c) 2021 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
178
+ [MIDI Parser](https://github.com/javier-sy/midi-parser) Copyright (c) 2021-2025 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
187
179
 
188
180
  [Nibbler](https://github.com/arirusso/nibbler) Copyright (c) 2011-2015 [Ari Russo](http://arirusso.com), licensed under Apache License 2.0 (see the file LICENSE.nibbler)
data/examples/usage.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  $:.unshift(File.join('..', 'lib'))
3
3
 
4
4
  #
5
- # Walk through different ways to use Nibbler
5
+ # Walk through different ways to use MIDI Parser
6
6
  #
7
7
 
8
8
  require 'midi-parser'
@@ -46,26 +46,4 @@ pp 'See progress'
46
46
 
47
47
  pp midi_parser.buffer # should give you an array of bits
48
48
 
49
- pp midi_parser.buffer_s # should give you an array of bytestrs
50
-
51
- pp 'Pass in a timestamp'
52
-
53
- # note:
54
- # once you pass in a timestamp for the first time, midi-parser.messages will then return
55
- # an array of message/timestamp hashes
56
- # if there was no timestamp for a particular message it will be nil
57
- #
58
-
59
- pp midi_parser.parse('904040', timestamp: Time.now.to_i)
60
-
61
- pp 'Add callbacks'
62
-
63
- # you can list any properties of the message to check against.
64
- # if they are all true, the callback will fire
65
- #
66
- # if you wish to use "or" or any more advanced matching I would just process the message after it"s
67
- # returned
68
- #
69
- midi_parser.when({ class: MIDIMessage::NoteOn }) { |msg| puts 'bark' }
70
- pp midi_parser.parse('904040')
71
- pp midi_parser.parse('804040')
49
+ pp midi_parser.buffer_s # should give you a hex string
@@ -1,28 +1,53 @@
1
1
  module MIDIParser
2
- # Accepts various types of input and returns an array of hex digit chars
2
+ # Normalizes various input formats into hex nibble strings for parsing.
3
3
  #
4
- # Ideally this would output Integer objects. However, given that Ruby numerics 0x0 and 0x00 result in the same
5
- # object (0 Integer), this would limit the parser to only working with bytes instead of both nibbles and bytes.
4
+ # DataProcessor accepts bytes, hex strings, nibbles, and arrays, converting
5
+ # them into a consistent format (uppercase hex character strings) that can
6
+ # be processed by the {Parser}.
6
7
  #
7
- # For example, if the input were "5" then the processor would return an ambiguous 0x5
8
+ # This module handles the complexity of accepting multiple input formats,
9
+ # allowing users to mix and match formats in a single parse call.
8
10
  #
11
+ # @note This outputs String objects rather than Integers because Ruby's
12
+ # 0x0 and 0x00 are the same Integer, which would make nibbles and bytes
13
+ # ambiguous.
14
+ #
15
+ # @example Processing bytes
16
+ # MIDIParser::DataProcessor.process([0x90, 0x40, 0x40])
17
+ # # => ["9", "0", "4", "0", "4", "0"]
18
+ #
19
+ # @example Processing hex string
20
+ # MIDIParser::DataProcessor.process(["904040"])
21
+ # # => ["9", "0", "4", "0", "4", "0"]
22
+ #
23
+ # @example Processing mixed input
24
+ # MIDIParser::DataProcessor.process(["9", "0", 0x40, 64])
25
+ # # => ["9", "0", "4", "0", "4", "0"]
26
+ #
27
+ # @api private
9
28
  module DataProcessor
10
29
  extend self
11
30
 
12
- # Accepts various types of input and returns an array of hex digit chars
13
- # Invalid input is disregarded
31
+ # Converts various input formats to an array of hex nibble strings.
32
+ #
33
+ # Invalid input (non-hex characters, out-of-range bytes) is filtered out.
14
34
  #
15
- # @param [*String, *Integer] args
16
- # @return [Array<String>] An array of hex string nibbles eg "6", "a"
35
+ # @param args [Array<String, Integer, Array>] input data in various formats
36
+ # @return [Array<String>] array of uppercase hex nibble strings (e.g., ["9", "0", "4", "0"])
37
+ #
38
+ # @example
39
+ # DataProcessor.process([0x90, "40", 0x40])
40
+ # # => ["9", "0", "4", "0", "4", "0"]
17
41
  def process(*args)
18
42
  args.map { |arg| convert(arg) }.flatten.compact.map(&:upcase)
19
43
  end
20
44
 
21
45
  private
22
46
 
23
- # Convert a single value to hex chars
24
- # @param [Array<Integer>, Array<String>, Integer, String] value
25
- # @return [Array<String>]
47
+ # Converts a single value to hex character strings.
48
+ #
49
+ # @param value [Array, String, Integer] value to convert
50
+ # @return [Array<String>, nil] hex character strings, or nil if invalid
26
51
  def convert(value)
27
52
  case value
28
53
  when Array then value.map { |arr| process(*arr) }.reduce(:+)
@@ -31,17 +56,18 @@ module MIDIParser
31
56
  end
32
57
  end
33
58
 
34
- # Limit the given number to bytes usable in MIDI ie values (0..240)
35
- # returns nil if the byte is outside of that range
36
- # @param [Integer] num
37
- # @return [Integer, nil]
59
+ # Filters numeric values to valid MIDI byte range.
60
+ #
61
+ # @param num [Integer] byte value to filter
62
+ # @return [Integer, nil] the value if valid (0x00-0xFF), nil otherwise
38
63
  def filter_numeric(num)
39
64
  num if (0x00..0xFF).include?(num)
40
65
  end
41
66
 
42
- # Only return valid hex string characters
43
- # @param [String] string
44
- # @return [String]
67
+ # Filters a string to only valid hex characters.
68
+ #
69
+ # @param string [String] input string
70
+ # @return [String] string with only hex characters (0-9, A-F)
45
71
  def filter_string(string)
46
72
  string.gsub(/[^0-9a-fA-F]/, '').upcase
47
73
  end
@@ -1,5 +1,13 @@
1
1
  module MIDIParser
2
+ # Factory for building MIDI message objects from parsed data.
3
+ #
4
+ # MessageBuilder maps MIDI status bytes to the appropriate MIDIEvents
5
+ # message classes and handles the construction of message objects.
6
+ #
7
+ # @api private
2
8
  class MessageBuilder
9
+ # Channel message type mappings.
10
+ # Maps status nibble to message class and expected nibble count.
3
11
  CHANNEL_MESSAGE = [
4
12
  {
5
13
  status: 0x8,
@@ -38,6 +46,8 @@ module MIDIParser
38
46
  }
39
47
  ].freeze
40
48
 
49
+ # System message type mappings.
50
+ # Maps status nibble to message class and expected nibble count.
41
51
  SYSTEM_MESSAGE = [
42
52
  {
43
53
  status: 0x1..0x6,
@@ -51,27 +61,52 @@ module MIDIParser
51
61
  }
52
62
  ].freeze
53
63
 
54
- attr_reader :num_nibbles, :name, :clazz
64
+ # @return [Integer] number of nibbles expected for this message type
65
+ attr_reader :num_nibbles
55
66
 
67
+ # @return [String, nil] optional name for the message type
68
+ attr_reader :name
69
+
70
+ # @return [Class] the MIDIEvents class to instantiate
71
+ attr_reader :clazz
72
+
73
+ # Builds a System Exclusive message from raw data.
74
+ #
75
+ # @param message_data [Array<Integer>] SysEx message bytes
76
+ # @return [MIDIEvents::SystemExclusive] the constructed message
56
77
  def self.build_system_exclusive(*message_data)
57
78
  MIDIEvents::SystemExclusive.new(*message_data)
58
79
  end
59
80
 
81
+ # Creates a MessageBuilder for a system message type.
82
+ #
83
+ # @param status [Integer] the second status nibble (0x1-0xF)
84
+ # @return [MessageBuilder] configured builder for the message type
60
85
  def self.for_system_message(status)
61
86
  type = SYSTEM_MESSAGE.find { |type| type[:status].cover?(status) }
62
87
  new(type[:nibbles], type[:class])
63
88
  end
64
89
 
90
+ # Creates a MessageBuilder for a channel message type.
91
+ #
92
+ # @param status [Integer] the first status nibble (0x8-0xE)
93
+ # @return [MessageBuilder] configured builder for the message type
65
94
  def self.for_channel_message(status)
66
95
  type = CHANNEL_MESSAGE.find { |type| type[:status] == status }
67
96
  new(type[:nibbles], type[:class])
68
97
  end
69
98
 
99
+ # @param num_nibbles [Integer] expected nibble count for this message type
100
+ # @param clazz [Class] MIDIEvents class to instantiate
70
101
  def initialize(num_nibbles, clazz)
71
102
  @num_nibbles = num_nibbles
72
103
  @clazz = clazz
73
104
  end
74
105
 
106
+ # Builds a MIDI message from the given data.
107
+ #
108
+ # @param message_data [Array<Integer>] message data bytes
109
+ # @return [MIDIEvents::ChannelMessage, MIDIEvents::SystemMessage] the constructed message
75
110
  def build(*message_data)
76
111
  @clazz.new(*message_data)
77
112
  end
@@ -1,15 +1,32 @@
1
1
  module MIDIParser
2
+ # Low-level MIDI message parser.
3
+ #
4
+ # Parser handles the actual parsing logic, converting hex nibbles into
5
+ # MIDI message objects. It maintains an internal buffer and supports
6
+ # MIDI running status.
7
+ #
8
+ # This class is typically not used directly. Use {Session} instead for
9
+ # a higher-level interface.
10
+ #
11
+ # @api private
2
12
  class Parser
13
+ # @return [Array<String>] the current buffer of hex nibble strings
3
14
  attr_reader :buffer
4
15
 
16
+ # Creates a new parser with empty buffer and running status.
5
17
  def initialize
6
18
  @running_status = RunningStatus.new
7
19
  @buffer = []
8
20
  end
9
21
 
10
- # Process the given nibbles and add them to the buffer
11
- # @param [Array<String, Integer>] nibbles
12
- # @return [Array<MIDIEvent>]
22
+ # Processes hex nibbles and returns any complete MIDI messages.
23
+ #
24
+ # Nibbles are accumulated in the buffer. When enough data is present
25
+ # to form a complete MIDI message, it is parsed and returned.
26
+ #
27
+ # @param nibbles [Array<String>] hex nibble strings (e.g., ["9", "0", "4", "0"])
28
+ # @return [Array<MIDIEvents::ChannelMessage, MIDIEvents::SystemMessage>]
29
+ # array of parsed messages
13
30
  def process(nibbles)
14
31
  messages = []
15
32
  pointer = 0
@@ -33,9 +50,10 @@ module MIDIParser
33
50
  messages
34
51
  end
35
52
 
36
- # If possible, convert the given fragment to a MIDI message
37
- # @param [Array<String>] fragment A fragment of data eg ["9", "0", "4", "0", "5", "0"]
38
- # @return [Hash, nil]
53
+ # Attempts to convert a fragment of nibbles to a MIDI message.
54
+ #
55
+ # @param fragment [Array<String>] hex nibble strings (e.g., ["9", "0", "4", "0"])
56
+ # @return [Hash, nil] hash with :message and :processed keys, or nil if incomplete
39
57
  def nibbles_to_message(fragment)
40
58
  if fragment.length >= 2
41
59
  # convert the part of the fragment to start with to a numeric
@@ -76,15 +94,15 @@ module MIDIParser
76
94
  @buffer[pointer, (@buffer.length - pointer)]
77
95
  end
78
96
 
79
- # If the given fragment has at least the given number of nibbles, use it to build a hash that can be used
80
- # to build a MIDI message
97
+ # Attempts to build a MIDI message from a fragment with enough nibbles.
81
98
  #
82
- # @param [Integer] num_nibbles
83
- # @param [Array<String>] fragment
84
- # @param [Hash] options
85
- # @option options [String] :status_nibble_2
86
- # @option options [Boolean] :recursive
87
- # @return [Hash, nil]
99
+ # @param fragment [Array<String>] hex nibble strings
100
+ # @param message_builder [MessageBuilder] builder for the message type
101
+ # @param options [Hash] additional options
102
+ # @option options [String] :status_nibble_2 cached status nibble for running status
103
+ # @option options [Boolean] :recursive whether to try shorter lengths
104
+ # @option options [Integer] :offset nibble offset adjustment
105
+ # @return [Hash, nil] hash with :message and :processed keys, or nil
88
106
  def lookahead(fragment, message_builder, options = {})
89
107
  offset = options.fetch(:offset, 0)
90
108
  num_nibbles = message_builder.num_nibbles + offset
@@ -127,6 +145,12 @@ module MIDIParser
127
145
  end
128
146
  end
129
147
 
148
+ # Manages MIDI running status state.
149
+ #
150
+ # Running status is a MIDI optimization where the status byte can be omitted
151
+ # if it's the same as the previous message.
152
+ #
153
+ # @api private
130
154
  class RunningStatus
131
155
  extend Forwardable
132
156
 
@@ -136,16 +160,24 @@ module MIDIParser
136
160
 
137
161
  def_delegators :@state, :[]
138
162
 
163
+ # Clears the running status state.
164
+ # @return [nil]
139
165
  def cancel
140
166
  @state = nil
141
167
  end
142
168
 
143
- # Is there an active cached running status?
144
- # @return [Boolean]
169
+ # Checks if running status is available.
170
+ # @return [Boolean] true if a previous status is cached
145
171
  def possible?
146
172
  !@state.nil?
147
173
  end
148
174
 
175
+ # Stores the running status state.
176
+ #
177
+ # @param offset [Integer] nibble offset for running status
178
+ # @param message_builder [MessageBuilder] builder for message type
179
+ # @param status_nibble_2 [String] second status nibble
180
+ # @return [Hash] the stored state
149
181
  def set(offset, message_builder, status_nibble_2)
150
182
  @state = {
151
183
  message_builder: message_builder,
@@ -1,35 +1,110 @@
1
1
  module MIDIParser
2
- # A parser session
2
+ # A parser session that maintains state between parse calls.
3
3
  #
4
- # Holds on to data that is not relevant to the parser between calls. For instance,
5
- # past messages, rejected bytes
4
+ # Session is the main interface for parsing MIDI data. It wraps the {Parser}
5
+ # and provides a convenient API for parsing various input formats and
6
+ # accessing the internal buffer.
6
7
  #
8
+ # The session maintains a buffer of unparsed nibbles between calls, allowing
9
+ # for incremental parsing of MIDI data as it arrives.
10
+ #
11
+ # @example Basic parsing
12
+ # session = MIDIParser::Session.new
13
+ # messages = session.parse(0x90, 0x40, 0x40)
14
+ # # => [#<MIDIEvents::NoteOn ...>]
15
+ #
16
+ # @example Incremental parsing
17
+ # session = MIDIParser::Session.new
18
+ # session.parse("90") # => []
19
+ # session.parse("40") # => []
20
+ # session.buffer # => ["9", "0", "4", "0"]
21
+ # session.parse("40") # => [#<MIDIEvents::NoteOn ...>]
22
+ #
23
+ # @example Mixed input types
24
+ # session = MIDIParser::Session.new
25
+ # session.parse("9", "0", 0x40, 64)
26
+ # # => [#<MIDIEvents::NoteOn ...>]
27
+ #
28
+ # @see MIDIParser.new Convenience constructor
29
+ # @see Parser The underlying parser
30
+ #
31
+ # @api public
7
32
  class Session
33
+ # Creates a new parser session.
34
+ #
35
+ # @example
36
+ # session = MIDIParser::Session.new
8
37
  def initialize
9
38
  @parser = Parser.new
10
39
  end
11
40
 
12
- # The buffer
13
- # @return [Array<Object>]
41
+ # Returns the current buffer contents as an array of hex nibble strings.
42
+ #
43
+ # The buffer contains unparsed MIDI data waiting for more bytes
44
+ # to complete a message.
45
+ #
46
+ # @return [Array<String>] array of hex nibble strings (e.g., ["9", "0", "4", "0"])
47
+ #
48
+ # @example
49
+ # session = MIDIParser::Session.new
50
+ # session.parse("90", "40")
51
+ # session.buffer # => ["9", "0", "4", "0"]
14
52
  def buffer
15
53
  @parser.buffer
16
54
  end
17
55
 
18
- # The buffer as a single hex string
19
- # @return [String]
56
+ # Returns the current buffer contents as a single hex string.
57
+ #
58
+ # @return [String] concatenated hex string of buffer contents
59
+ #
60
+ # @example
61
+ # session = MIDIParser::Session.new
62
+ # session.parse("90", "40")
63
+ # session.buffer_s # => "9040"
20
64
  def buffer_s
21
65
  @parser.buffer.join
22
66
  end
23
67
  alias buffer_hex buffer_s
24
68
 
25
- # Clear the parser buffer
69
+ # Clears the parser buffer, discarding any unparsed data.
70
+ #
71
+ # @return [Array] empty array
72
+ #
73
+ # @example
74
+ # session = MIDIParser::Session.new
75
+ # session.parse("90", "40")
76
+ # session.clear_buffer
77
+ # session.buffer # => []
26
78
  def clear_buffer
27
79
  @parser.buffer.clear
28
80
  end
29
81
 
30
- # Parse some input
31
- # @param [*Object] args
32
- # @return [Array<MIDIEvent>]
82
+ # Parses MIDI data and returns any complete messages.
83
+ #
84
+ # Accepts various input formats: bytes (Integer), hex strings (String),
85
+ # nibbles, or arrays. Input is accumulated in an internal buffer until
86
+ # complete MIDI messages can be formed.
87
+ #
88
+ # @param args [Array<Integer, String>] MIDI data in various formats:
89
+ # - Bytes as integers (e.g., 0x90, 0x40, 0x40)
90
+ # - Hex strings (e.g., "904040" or "90", "40", "40")
91
+ # - Nibbles as strings (e.g., "9", "0", "4", "0")
92
+ # - Mixed formats
93
+ #
94
+ # @return [Array<MIDIEvents::ChannelMessage, MIDIEvents::SystemMessage>]
95
+ # array of parsed MIDI message objects (empty if no complete messages)
96
+ #
97
+ # @example Parse bytes
98
+ # session.parse(0x90, 0x40, 0x40)
99
+ # # => [#<MIDIEvents::NoteOn @channel=0, @note=64, @velocity=64>]
100
+ #
101
+ # @example Parse hex string
102
+ # session.parse("904040")
103
+ # # => [#<MIDIEvents::NoteOn ...>]
104
+ #
105
+ # @example Parse multiple messages
106
+ # session.parse("90404080404000")
107
+ # # => [#<MIDIEvents::NoteOn ...>, #<MIDIEvents::NoteOff ...>]
33
108
  def parse(*args)
34
109
  queue = DataProcessor.process(args)
35
110
  @parser.process(queue)
@@ -1,12 +1,29 @@
1
1
  module MIDIParser
2
- # A helper for converting between different types of nibbles and bytes
2
+ # Utility module for converting between hex strings, nibbles, and bytes.
3
+ #
4
+ # TypeConversion provides methods to transform MIDI data between different
5
+ # representations: hex character strings, numeric nibbles, and numeric bytes.
6
+ #
7
+ # @example Converting hex string to bytes
8
+ # TypeConversion.hex_str_to_numeric_bytes("904040")
9
+ # # => [0x90, 0x40, 0x40]
10
+ #
11
+ # @example Converting bytes to nibbles
12
+ # TypeConversion.numeric_byte_to_numeric_nibbles(0x90)
13
+ # # => [0x9, 0x0]
14
+ #
15
+ # @api private
3
16
  module TypeConversion
4
17
  extend self
5
18
 
6
- # Converts an array of hex nibble strings to numeric bytes
7
- # eg ["9", "0", "5", "0", "4", "0"] => [0x90, 0x50, 0x40]
8
- # @param [Array<String>] nibbles
9
- # @return [Array<Integer>]
19
+ # Converts hex nibble strings to numeric bytes.
20
+ #
21
+ # @param nibbles [Array<String>] hex character strings (e.g., ["9", "0", "4", "0"])
22
+ # @return [Array<Integer>] numeric bytes (e.g., [0x90, 0x40])
23
+ #
24
+ # @example
25
+ # hex_chars_to_numeric_bytes(["9", "0", "4", "0", "4", "0"])
26
+ # # => [0x90, 0x40, 0x40]
10
27
  def hex_chars_to_numeric_bytes(nibbles)
11
28
  nibbles = nibbles.dup
12
29
  # get rid of last nibble if there's an odd number
@@ -20,51 +37,77 @@ module MIDIParser
20
37
  bytes
21
38
  end
22
39
 
23
- # Converts a string of hex digits to string nibbles
24
- # eg "905040" => ["9", "0", "5", "0", "4", "0"]
25
- # @param [String] string
26
- # @return [Array<String>]
40
+ # Splits a hex string into individual character strings.
41
+ #
42
+ # @param string [String] hex digit string (e.g., "904040")
43
+ # @return [Array<String>] individual hex characters (e.g., ["9", "0", "4", "0", "4", "0"])
44
+ #
45
+ # @example
46
+ # hex_str_to_hex_chars("904040")
47
+ # # => ["9", "0", "4", "0", "4", "0"]
27
48
  def hex_str_to_hex_chars(string)
28
49
  string.split(//)
29
50
  end
30
51
 
31
- # Converts a string of hex digits to numeric nibbles
32
- # eg "905040" => [0x9, 0x0, 0x5, 0x0, 0x4, 0x0]
33
- # @param [String] string
34
- # @return [Array<String>]
52
+ # Converts a hex string to numeric nibbles.
53
+ #
54
+ # @param string [String] hex digit string (e.g., "904040")
55
+ # @return [Array<Integer>] numeric nibbles (e.g., [0x9, 0x0, 0x4, 0x0, 0x4, 0x0])
56
+ #
57
+ # @example
58
+ # hex_str_to_numeric_nibbles("904040")
59
+ # # => [9, 0, 4, 0, 4, 0]
35
60
  def hex_str_to_numeric_nibbles(string)
36
61
  bytes = hex_str_to_numeric_bytes(string)
37
62
  numeric_bytes_to_numeric_nibbles(bytes)
38
63
  end
39
64
 
40
- # Converts a string of hex digits to numeric bytes
41
- # eg "905040" => [0x90, 0x50, 0x40]
42
- # @param [String] string
43
- # @return [Array<String>]
65
+ # Converts a hex string to numeric bytes.
66
+ #
67
+ # @param string [String] hex digit string (e.g., "904040")
68
+ # @return [Array<Integer>] numeric bytes (e.g., [0x90, 0x40, 0x40])
69
+ #
70
+ # @example
71
+ # hex_str_to_numeric_bytes("904040")
72
+ # # => [144, 64, 64]
44
73
  def hex_str_to_numeric_bytes(string)
45
74
  chars = hex_str_to_hex_chars(string)
46
75
  hex_chars_to_numeric_bytes(chars)
47
76
  end
48
77
 
49
- # Converts an array bytes to an array of nibbles
50
- # eg [0x90, 0x50, 0x40] => [0x9, 0x0, 0x5, 0x0, 0x4, 0x0]
51
- # @param [Array<Integer>] bytes
52
- # @return [Array<String>]
78
+ # Converts numeric bytes to numeric nibbles.
79
+ #
80
+ # @param bytes [Array<Integer>] byte values (e.g., [0x90, 0x40])
81
+ # @return [Array<Integer>] nibble values (e.g., [0x9, 0x0, 0x4, 0x0])
82
+ #
83
+ # @example
84
+ # numeric_bytes_to_numeric_nibbles([0x90, 0x40, 0x40])
85
+ # # => [9, 0, 4, 0, 4, 0]
53
86
  def numeric_bytes_to_numeric_nibbles(bytes)
54
87
  bytes.map { |byte| numeric_byte_to_numeric_nibbles(byte) }.flatten
55
88
  end
56
89
 
57
- # Converts a numeric byte to an array of hex nibble strings eg 0x90 => ["9", "0"]
58
- # @param [Integer] num
59
- # @return [Array<String>]
90
+ # Converts a numeric byte to hex character strings.
91
+ #
92
+ # @param num [Integer] byte value (e.g., 0x90)
93
+ # @return [Array<String>] hex characters (e.g., ["9", "0"])
94
+ #
95
+ # @example
96
+ # numeric_byte_to_hex_chars(0x90)
97
+ # # => ["9", "0"]
60
98
  def numeric_byte_to_hex_chars(num)
61
99
  nibbles = numeric_byte_to_numeric_nibbles(num)
62
100
  nibbles.map { |n| n.to_s(16) }
63
101
  end
64
102
 
65
- # Converts a numeric byte to an array of numeric nibbles eg 0x90 => [0x9, 0x0]
66
- # @param [Integer] num
67
- # @return [Array<String>]
103
+ # Converts a numeric byte to numeric nibbles.
104
+ #
105
+ # @param num [Integer] byte value (e.g., 0x90)
106
+ # @return [Array<Integer>] nibble values (e.g., [0x9, 0x0])
107
+ #
108
+ # @example
109
+ # numeric_byte_to_numeric_nibbles(0x90)
110
+ # # => [9, 0]
68
111
  def numeric_byte_to_numeric_nibbles(num)
69
112
  [((num & 0xF0) >> 4), (num & 0x0F)]
70
113
  end
@@ -0,0 +1,3 @@
1
+ module MIDIParser
2
+ VERSION = '0.5.0'.freeze
3
+ end