midi-nibbler 0.2.3 → 0.2.4

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: 7bffa717264483d31e4f80d8be90a7d2908a003b
4
- data.tar.gz: d62d625eebbfa98b39acef4620cc87a85912d8bf
3
+ metadata.gz: 022b8e486f76d1621f5d3a73ad6eb3dff9764b6a
4
+ data.tar.gz: a030a1248bf74224ae98c85138ca146bbf07d1d0
5
5
  SHA512:
6
- metadata.gz: d2dd5f9de0c90bff1137a274369569692d3451782ce16687132413cb4869abd634354d29b98306b56d79f03f6d1ba30a4d4b901f92d5af535ac6dfda5f96a1e5
7
- data.tar.gz: 91602c10acafb0014f072a501c27c62a0f9f2681622e138b0ff3d643e9a357bd3101be0cf17e3ba9f00a4a91c1d762bb3d071416efa43360ba1cced86c831546
6
+ metadata.gz: 0fee2f393f46e64144d0f0c12a9421e6bcf86781bcf870b4d9af586cd3e284266f27678ffa985d6d80730e0e865ccfbddfe838729ff40c3810f49562497bcdb0
7
+ data.tar.gz: 418ece8a8a607b38c350342aa197ba94e636b3e138b0e40e66a2e87499fc4356a9257722d0d0307a8ecb36fb025e691e9cfeaef72b2d56e12ca106c2182a8b8b
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
@@ -1,9 +1,9 @@
1
1
  # Nibbler
2
2
 
3
- ![nibbler](http://img17.imageshack.us/img17/1713/dogwithsynth.jpg)
3
+ ![nibbler](http://i.imgur.com/4BFZPJY.png)
4
4
 
5
5
  Parse MIDI Messages
6
-
6
+
7
7
  ## Install
8
8
 
9
9
  `gem install midi-nibbler`
@@ -16,12 +16,12 @@ or using Bundler, add this to your Gemfile
16
16
 
17
17
  ```ruby
18
18
  require 'nibbler'
19
-
19
+
20
20
  nibbler = Nibbler.new
21
21
  ```
22
22
 
23
23
  Enter a message piece by piece
24
-
24
+
25
25
  ```ruby
26
26
  nibbler.parse("90")
27
27
  => nil
@@ -30,31 +30,31 @@ nibbler.parse("40")
30
30
  => nil
31
31
 
32
32
  nibbler.parse("40")
33
- => #<MIDIMessage::NoteOn:0x98c9818
34
- @channel=0,
35
- @data=[64, 100],
36
- @name="C3",
37
- @note=64,
38
- @status=[9, 0],
39
- @velocity=100,
33
+ => #<MIDIMessage::NoteOn:0x98c9818
34
+ @channel=0,
35
+ @data=[64, 100],
36
+ @name="C3",
37
+ @note=64,
38
+ @status=[9, 0],
39
+ @velocity=100,
40
40
  @verbose_name="Note On: C3">
41
41
  ```
42
-
42
+
43
43
  Enter a message all at once
44
-
44
+
45
45
  ```ruby
46
46
  nibbler.parse("904040")
47
-
48
- => #<MIDIMessage::NoteOn:0x98c9818
49
- @channel=0,
50
- @data=[64, 100],
51
- @name="C3",
52
- @note=64,
53
- @status=[9, 0],
54
- @velocity=100,
47
+
48
+ => #<MIDIMessage::NoteOn:0x98c9818
49
+ @channel=0,
50
+ @data=[64, 100],
51
+ @name="C3",
52
+ @note=64,
53
+ @status=[9, 0],
54
+ @velocity=100,
55
55
  @verbose_name="Note On: C3">
56
56
  ```
57
-
57
+
58
58
  Use bytes
59
59
 
60
60
  ```ruby
@@ -63,14 +63,14 @@ nibbler.parse(0x90, 0x40, 0x40)
63
63
  ```
64
64
 
65
65
  You can use nibbles in string format
66
-
66
+
67
67
  ```ruby
68
68
  nibbler.parse("9", "0", "4", "0", "4", "0")
69
69
  => #<MIDIMessage::NoteOn:0x98c9818 ...>
70
70
  ```
71
71
 
72
72
  Interchange the different types
73
-
73
+
74
74
  ```ruby
75
75
  nibbler.parse("9", "0", 0x40, 64)
76
76
  => #<MIDIMessage::NoteOn:0x98c9818 ...>
@@ -84,7 +84,7 @@ nibbler.parse(0x40, 64)
84
84
  ```
85
85
 
86
86
  Look at the messages we've parsed
87
-
87
+
88
88
  ```ruby
89
89
  nibbler.messages
90
90
  => [#<MIDIMessage::NoteOn:0x98c9804 ...>
@@ -92,7 +92,7 @@ nibbler.messages
92
92
  ```
93
93
 
94
94
  Add an incomplete message
95
-
95
+
96
96
  ```ruby
97
97
  nibbler.parse("9")
98
98
  nibbler.parse("40")
@@ -107,7 +107,7 @@ nibbler.buffer
107
107
  nibbler.buffer_s
108
108
  => "940"
109
109
  ```
110
-
110
+
111
111
  Pass in a timestamp
112
112
 
113
113
  ```ruby
@@ -119,7 +119,7 @@ Nibbler defaults to generate [midi-message](http://github.com/arirusso/midi-mess
119
119
 
120
120
  ```ruby
121
121
  Nibbler.new(:message_lib => :midilib)
122
-
122
+
123
123
  nibbler.parse("9", "0", 0x40, "40")
124
124
  => "0: ch 00 on 40 40"
125
125
  ```
@@ -136,4 +136,4 @@ nibbler.parse("9", "0", 0x40, "40")
136
136
 
137
137
  Apache 2.0, See the file LICENSE
138
138
 
139
- Copyright (c) 2011-2014 Ari Russo
139
+ Copyright (c) 2011-2015 Ari Russo
@@ -1,16 +1,19 @@
1
1
  #
2
+ # Nibbler
2
3
  # Parse MIDI Messages
3
- # (c)2011-2014 Ari Russo and licensed under the Apache 2.0 License
4
+ # (c)2011-2015 Ari Russo and licensed under the Apache 2.0 License
4
5
  #
5
6
 
6
7
  # libs
7
8
  require "forwardable"
8
9
 
9
10
  # modules
10
- require "nibbler/hex_processor"
11
+ require "nibbler/data_processor"
11
12
  require "nibbler/type_conversion"
12
13
 
13
14
  # classes
15
+ require "nibbler/message_builder"
16
+ require "nibbler/message_library"
14
17
  require "nibbler/parser"
15
18
  require "nibbler/session"
16
19
 
@@ -19,7 +22,7 @@ require "nibbler/session"
19
22
  #
20
23
  module Nibbler
21
24
 
22
- VERSION = "0.2.3"
25
+ VERSION = "0.2.4"
23
26
 
24
27
  # Shortcut to a new parser session
25
28
  def self.new(*a, &block)
@@ -1,47 +1,53 @@
1
- module Nibbler
2
-
1
+ module Nibbler
2
+
3
3
  # Accepts various types of input and returns an array of hex digit chars
4
- module HexProcessor
4
+ #
5
+ # Ideally this would output Integer objects. However, given that Ruby numerics 0x0 and 0x00 result in the same
6
+ # object (0 Integer), this would limit the parser to only working with bytes instead of both nibbles and bytes.
7
+ #
8
+ # For example, if the input were "5" then the processor would return an ambiguous 0x5
9
+ #
10
+ module DataProcessor
5
11
 
6
12
  extend self
7
-
13
+
8
14
  # Accepts various types of input and returns an array of hex digit chars
9
15
  # Invalid input is disregarded
10
16
  #
11
- # @param [*String, *Fixnum] args
17
+ # @param [*String, *Integer] args
12
18
  # @return [Array<String>] An array of hex string nibbles eg "6", "a"
13
19
  def process(*args)
14
- args.map { |arg| convert(arg) }.flatten.compact.map(&:upcase)
20
+ args.map { |arg| convert(arg) }.flatten.compact.map(&:upcase)
15
21
  end
16
-
22
+
17
23
  private
18
24
 
19
25
  # Convert a single value to hex chars
20
- # @param [Array<Fixnum>, Array<String>, Fixnum, String] value
26
+ # @param [Array<Integer>, Array<String>, Integer, String] value
21
27
  # @return [Array<String>]
22
28
  def convert(value)
23
29
  case value
24
30
  when Array then value.map { |arr| process(*arr) }.reduce(:+)
25
31
  when String then TypeConversion.hex_str_to_hex_chars(filter_string(value))
26
- when Fixnum then TypeConversion.numeric_byte_to_hex_chars(filter_numeric(value))
32
+ when Integer then TypeConversion.numeric_byte_to_hex_chars(filter_numeric(value))
27
33
  end
28
34
  end
29
-
35
+
30
36
  # Limit the given number to bytes usable in MIDI ie values (0..240)
31
37
  # returns nil if the byte is outside of that range
32
- # @param [Fixnum] num
33
- # @return [Fixnum, nil]
38
+ # @param [Integer] num
39
+ # @return [Integer, nil]
34
40
  def filter_numeric(num)
35
41
  num if (0x00..0xFF).include?(num)
36
42
  end
37
-
43
+
38
44
  # Only return valid hex string characters
39
45
  # @param [String] string
40
46
  # @return [String]
41
47
  def filter_string(string)
42
48
  string.gsub(/[^0-9a-fA-F]/, "").upcase
43
49
  end
44
-
50
+
45
51
  end
46
52
 
47
53
  end
@@ -0,0 +1,84 @@
1
+ module Nibbler
2
+
3
+ class MessageBuilder
4
+
5
+ CHANNEL_MESSAGE = [
6
+ {
7
+ :status => 0x8,
8
+ :name => :note_off,
9
+ :nibbles => 6
10
+ },
11
+ {
12
+ :status => 0x9,
13
+ :name => :note_on,
14
+ :nibbles => 6
15
+ },
16
+ {
17
+ :status => 0xA,
18
+ :name => :polyphonic_aftertouch,
19
+ :nibbles => 6
20
+ },
21
+ {
22
+ :status => 0xB,
23
+ :name => :control_change,
24
+ :nibbles => 6
25
+ },
26
+ {
27
+ :status => 0xC,
28
+ :name => :program_change,
29
+ :nibbles => 4
30
+ },
31
+ {
32
+ :status => 0xD,
33
+ :name => :channel_aftertouch,
34
+ :nibbles => 4
35
+ },
36
+ {
37
+ :status => 0xE,
38
+ :name => :pitch_bend,
39
+ :nibbles => 6
40
+ }
41
+ ].freeze
42
+
43
+ SYSTEM_MESSAGE = [
44
+ {
45
+ :status => 0x1..0x6,
46
+ :name => :system_common,
47
+ :nibbles => 6
48
+ },
49
+ {
50
+ :status => 0x8..0xF,
51
+ :name => :system_realtime,
52
+ :nibbles => 2
53
+ }
54
+ ].freeze
55
+
56
+ attr_reader :num_nibbles, :name
57
+
58
+ def self.build_system_exclusive(library, *message_data)
59
+ library.system_exclusive(*message_data)
60
+ end
61
+
62
+ def self.for_system_message(library, status)
63
+ type = SYSTEM_MESSAGE.find { |type| type[:status].cover?(status) }
64
+ new(library, type[:name], type[:nibbles])
65
+ end
66
+
67
+ def self.for_channel_message(library, status)
68
+ type = CHANNEL_MESSAGE.find { |type| type[:status] == status }
69
+ new(library, type[:name], type[:nibbles])
70
+ end
71
+
72
+ def initialize(library, name, num_nibbles)
73
+ @library = library
74
+ @name = name
75
+ @num_nibbles = num_nibbles
76
+ end
77
+
78
+ def build(*message_data)
79
+ @library.send(@name, *message_data)
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,21 @@
1
+ module Nibbler
2
+
3
+ class MessageLibrary
4
+
5
+ # MIDI message object library adapter
6
+ # @param [Symbol] lib The MIDI message library module eg MIDIMessage or Midilib
7
+ # @return [Module]
8
+ def self.adapter(lib = nil)
9
+ case lib
10
+ when :midilib then
11
+ require "nibbler/midilib"
12
+ ::Nibbler::Midilib
13
+ else
14
+ require "nibbler/midi-message"
15
+ ::Nibbler::MIDIMessage
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -4,143 +4,165 @@ module Nibbler
4
4
 
5
5
  attr_reader :buffer
6
6
 
7
- # @param [Hash] options
8
- # @option options [Symbol] :message_lib
9
- def initialize(options = {})
10
- @running_status = nil
7
+ def initialize(library)
8
+ @library = library
9
+ @running_status = RunningStatus.new
11
10
  @buffer = []
12
- @iterator = 0
13
-
14
- initialize_message_library(options[:message_lib])
15
11
  end
16
12
 
17
- # @param [Array<String, Fixnum>] nibbles
13
+ # Process the given nibbles and add them to the buffer
14
+ # @param [Array<String, Integer>] nibbles
18
15
  # @return [Hash]
19
16
  def process(nibbles)
20
- report = {
21
- :messages => [],
22
- :processed => [],
23
- :rejected => []
24
- }
25
- @iterator = 0
26
- @buffer += nibbles
27
- while @iterator <= (@buffer.length - 1)
28
- # iterate through nibbles until a status message is found
29
- # see if there really is a message there
30
- populate_current
31
- # current is the current piece of the buffer we"re dealing with
32
- unless (processed = nibbles_to_message).nil?
33
- # if it"s a real message, reject previous nibbles
34
- report[:rejected] += @buffer.slice(0, @iterator)
35
- # and record it
36
- @buffer = @current # current now has the remaining nibbles for next pass
37
- @current = nil # reset current
38
- @iterator = 0 # reset iterator
17
+ report = {
18
+ :messages => [],
19
+ :processed => [],
20
+ :rejected => []
21
+ }
22
+ pointer = 0
23
+ @buffer += nibbles
24
+ # Iterate through nibbles in the buffer until a status message is found
25
+ while pointer <= (@buffer.length - 1)
26
+ # fragment is the piece of the buffer to look at
27
+ fragment = get_fragment(pointer)
28
+ # See if there really is a message there
29
+ unless (processed = nibbles_to_message(fragment)).nil?
30
+ # if fragment contains a real message, reject the nibbles that precede it
31
+ report[:rejected] += @buffer.slice(0, pointer)
32
+ # and record it
33
+ @buffer = fragment.dup # fragment now has the remaining nibbles for next pass
34
+ fragment = nil # Reset fragment
35
+ pointer = 0 # Reset iterator
39
36
  report[:messages] << processed[:message]
40
- report[:processed] += processed[:processed]
37
+ report[:processed] += processed[:processed]
41
38
  else
42
- @running_status = nil
43
- @iterator += 1
44
- end
39
+ @running_status.cancel
40
+ pointer += 1
41
+ end
45
42
  end
46
43
  report
47
44
  end
48
45
 
46
+ # If possible, convert the given fragment to a MIDI message
47
+ # @param [Array<String>] fragment A fragment of data eg ["9", "0", "4", "0", "5", "0"]
49
48
  # @return [Hash, nil]
50
- def nibbles_to_message
51
- if @current.length >= 2
52
- nibbles = @current.slice(0..1).map(&:hex)
53
- compute_message(nibbles)
49
+ def nibbles_to_message(fragment)
50
+ if fragment.length >= 2
51
+ # convert the part of the fragment to start with to a numeric
52
+ slice = fragment.slice(0..1).map(&:hex)
53
+ compute_message(slice, fragment)
54
54
  end
55
55
  end
56
56
 
57
57
  private
58
58
 
59
- # @param [Array<Fixnum>] nibbles
59
+ # Attempt to convert the given nibbles into a MIDI message
60
+ # @param [Array<Integer>] nibbles
60
61
  # @return [Hash, nil]
61
- def compute_message(nibbles)
62
+ def compute_message(nibbles, fragment)
62
63
  case nibbles[0]
63
- when 0x8 then lookahead(6) { |status_2, bytes| @message.note_off(status_2, bytes[1], bytes[2]) }
64
- when 0x9 then lookahead(6) { |status_2, bytes| @message.note_on(status_2, bytes[1], bytes[2]) }
65
- when 0xA then lookahead(6) { |status_2, bytes| @message.polyphonic_aftertouch(status_2, bytes[1], bytes[2]) }
66
- when 0xB then lookahead(6) { |status_2, bytes| @message.control_change(status_2, bytes[1], bytes[2]) }
67
- when 0xC then lookahead(4) { |status_2, bytes| @message.program_change(status_2, bytes[1]) }
68
- when 0xD then lookahead(4) { |status_2, bytes| @message.channel_aftertouch(status_2, bytes[1]) }
69
- when 0xE then lookahead(6) { |status_2, bytes| @message.pitch_bend(status_2, bytes[1], bytes[2]) }
70
- when 0xF then
64
+ when 0x8..0xE then lookahead(fragment, MessageBuilder.for_channel_message(@library, nibbles[0]))
65
+ when 0xF then
71
66
  case nibbles[1]
72
- when 0x0 then lookahead_sysex { |bytes| @message.system_exclusive(*bytes) }
73
- when 0x1..0x6 then lookahead(6, :recursive => true) { |status_2, bytes| @message.system_common(status_2, bytes[1], bytes[2]) }
74
- when 0x8..0xF then lookahead(2) { |status_2, bytes| @message.system_realtime(status_2) }
67
+ when 0x0 then lookahead_for_sysex(fragment)
68
+ else lookahead(fragment, MessageBuilder.for_system_message(@library, nibbles[1]), :recursive => true)
75
69
  end
76
70
  else
77
- use_running_status if possible_running_status?
71
+ lookahead_using_running_status(fragment) if @running_status.possible?
78
72
  end
79
73
  end
80
74
 
81
- # Choose a MIDI message object library
82
- def initialize_message_library(lib)
83
- @message = case lib
84
- when :midilib then
85
- require "nibbler/midilib"
86
- ::Nibbler::Midilib
87
- else
88
- require "nibbler/midi-message"
89
- ::Nibbler::MIDIMessage
90
- end
75
+ # Attempt to convert the fragment to a MIDI message using the given fragment and cached running status
76
+ # @param [Array<String>] fragment A fragment of data eg ["4", "0", "5", "0"]
77
+ # @return [Hash, nil]
78
+ def lookahead_using_running_status(fragment)
79
+ lookahead(fragment, @running_status[:message_builder], :offset => @running_status[:offset], :status_nibble_2 => @running_status[:status_nibble_2])
91
80
  end
92
81
 
93
- def possible_running_status?
94
- !@running_status.nil?
82
+ # Get the data in the buffer for the given pointer
83
+ # @param [Integer] pointer
84
+ # @return [Array<String>]
85
+ def get_fragment(pointer)
86
+ @buffer[pointer, (@buffer.length - pointer)]
95
87
  end
96
88
 
97
- def use_running_status
98
- lookahead(@running_status[:num], :status_nibble => @running_status[:status_nibble], &@running_status[:callback])
99
- end
100
-
101
- def populate_current
102
- @current = (@buffer[@iterator, (@buffer.length - @iterator)])
103
- end
104
-
105
- def lookahead(num, options = {}, &callback)
106
- # do we have enough nibbles for num bytes?
107
- if @current.size >= num
89
+ # If the given fragment has at least the given number of nibbles, use it to build a hash that can be used
90
+ # to build a MIDI message
91
+ #
92
+ # @param [Integer] num_nibbles
93
+ # @param [Array<String>] fragment
94
+ # @param [Hash] options
95
+ # @option options [String] :status_nibble_2
96
+ # @option options [Boolean] :recursive
97
+ # @return [Hash, nil]
98
+ def lookahead(fragment, message_builder, options = {})
99
+ offset = options.fetch(:offset, 0)
100
+ num_nibbles = message_builder.num_nibbles + offset
101
+ if fragment.size >= num_nibbles
108
102
  # if so shift those nibbles off of the array and call block with them
109
- nibbles = @current.slice!(0, num)
110
- status_nibble ||= options[:status_nibble] || nibbles[1]
103
+ nibbles = fragment.slice!(0, num_nibbles)
104
+ status_nibble_2 ||= options[:status_nibble_2] || nibbles[1]
111
105
 
112
- # send the nibbles to the block as bytes
113
- # return the evaluated block and the remaining nibbles
106
+ # send the nibbles to the block as bytes
107
+ # return the evaluated block and the remaining nibbles
114
108
  bytes = TypeConversion.hex_chars_to_numeric_bytes(nibbles)
109
+ bytes = bytes[1..-1] if options[:status_nibble_2].nil?
115
110
 
116
- # record the current situation in case running status comes up next round
117
- @running_status = {
118
- :callback => callback,
119
- :num => num - 2,
120
- :status_nibble => status_nibble
121
- }
111
+ # record the fragment situation in case running status comes up next round
112
+ @running_status.set(offset - 2, message_builder, status_nibble_2)
113
+
114
+ message_args = [status_nibble_2.hex]
115
+ message_args += bytes if num_nibbles > 2
122
116
 
117
+ message = message_builder.build(*message_args)
123
118
  {
124
- :message => yield(status_nibble.hex, bytes),
119
+ :message => message,
125
120
  :processed => nibbles
126
121
  }
127
- elsif num > 0 && !!options[:recursive]
128
- lookahead(num - 2, options, &callback)
122
+ elsif num_nibbles > 0 && !!options[:recursive]
123
+ lookahead(fragment, message_builder, options.merge({ :offset => offset - 2 }))
129
124
  end
130
125
  end
131
126
 
132
- def lookahead_sysex(&block)
133
- @running_status = nil
134
-
135
- bytes = TypeConversion.hex_chars_to_numeric_bytes(@current)
127
+ def lookahead_for_sysex(fragment)
128
+ @running_status.cancel
129
+ bytes = TypeConversion.hex_chars_to_numeric_bytes(fragment)
136
130
  unless (index = bytes.index(0xF7)).nil?
131
+ message_data = bytes.slice!(0, index + 1)
132
+ message = MessageBuilder.build_system_exclusive(@library, *message_data)
137
133
  {
138
- :message => yield(bytes.slice!(0, index + 1)),
139
- :processed => @current.slice!(0, (index + 1) * 2)
134
+ :message => message,
135
+ :processed => fragment.slice!(0, (index + 1) * 2)
140
136
  }
141
137
  end
142
138
  end
143
139
 
140
+ class RunningStatus
141
+
142
+ extend Forwardable
143
+
144
+ def_delegators :@state, :[]
145
+
146
+ def cancel
147
+ @state = nil
148
+ end
149
+
150
+ # Is there an active cached running status?
151
+ # @return [Boolean]
152
+ def possible?
153
+ !@state.nil?
154
+ end
155
+
156
+ def set(offset, message_builder, status_nibble_2)
157
+ @state = {
158
+ :message_builder => message_builder,
159
+ :offset => offset,
160
+ :status_nibble_2 => status_nibble_2
161
+ }
162
+ end
163
+
164
+ end
165
+
144
166
  end
145
167
 
146
168
  end