midi-nibbler 0.2.3 → 0.2.4

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