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 +4 -4
- data/LICENSE +1 -1
- data/README.md +29 -29
- data/lib/nibbler.rb +6 -3
- data/lib/nibbler/{hex_processor.rb → data_processor.rb} +20 -14
- data/lib/nibbler/message_builder.rb +84 -0
- data/lib/nibbler/message_library.rb +21 -0
- data/lib/nibbler/parser.rb +113 -91
- data/lib/nibbler/session.rb +27 -25
- data/lib/nibbler/type_conversion.rb +47 -11
- data/test/data_processor_test.rb +146 -0
- data/test/functional_buffer_test.rb +64 -0
- data/test/functional_rejected_test.rb +154 -0
- data/test/helper.rb +2 -0
- data/test/message_library_test.rb +37 -0
- data/test/midi_message_test.rb +201 -105
- data/test/parser_test.rb +221 -149
- data/test/type_conversion_test.rb +116 -17
- metadata +11 -8
- data/test/hex_processor_test.rb +0 -56
- data/test/parser_buffer_test.rb +0 -66
- data/test/parser_rejected_test.rb +0 -60
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 022b8e486f76d1621f5d3a73ad6eb3dff9764b6a
|
|
4
|
+
data.tar.gz: a030a1248bf74224ae98c85138ca146bbf07d1d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fee2f393f46e64144d0f0c12a9421e6bcf86781bcf870b4d9af586cd3e284266f27678ffa985d6d80730e0e865ccfbddfe838729ff40c3810f49562497bcdb0
|
|
7
|
+
data.tar.gz: 418ece8a8a607b38c350342aa197ba94e636b3e138b0e40e66a2e87499fc4356a9257722d0d0307a8ecb36fb025e691e9cfeaef72b2d56e12ca106c2182a8b8b
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Nibbler
|
|
2
2
|
|
|
3
|
-

|
|
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-
|
|
139
|
+
Copyright (c) 2011-2015 Ari Russo
|
data/lib/nibbler.rb
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#
|
|
2
|
+
# Nibbler
|
|
2
3
|
# Parse MIDI Messages
|
|
3
|
-
# (c)2011-
|
|
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/
|
|
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.
|
|
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
|
-
|
|
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, *
|
|
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<
|
|
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
|
|
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 [
|
|
33
|
-
# @return [
|
|
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
|
data/lib/nibbler/parser.rb
CHANGED
|
@@ -4,143 +4,165 @@ module Nibbler
|
|
|
4
4
|
|
|
5
5
|
attr_reader :buffer
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
26
|
-
@buffer += nibbles
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
unless (processed = nibbles_to_message).nil?
|
|
33
|
-
# if
|
|
34
|
-
report[:rejected] += @buffer.slice(0,
|
|
35
|
-
# and record it
|
|
36
|
-
@buffer =
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
43
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
#
|
|
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(
|
|
64
|
-
when
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
71
|
+
lookahead_using_running_status(fragment) if @running_status.possible?
|
|
78
72
|
end
|
|
79
73
|
end
|
|
80
74
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 =
|
|
110
|
-
|
|
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
|
|
117
|
-
@running_status
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 =>
|
|
119
|
+
:message => message,
|
|
125
120
|
:processed => nibbles
|
|
126
121
|
}
|
|
127
|
-
elsif
|
|
128
|
-
lookahead(
|
|
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
|
|
133
|
-
@running_status
|
|
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 =>
|
|
139
|
-
:processed =>
|
|
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
|