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
data/lib/nibbler/session.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Nibbler
|
2
|
-
|
2
|
+
|
3
3
|
# A parser session
|
4
4
|
#
|
5
5
|
# Holds on to data that is not relevant to the parser between calls. For instance,
|
@@ -12,26 +12,28 @@ module Nibbler
|
|
12
12
|
attr_reader :messages,
|
13
13
|
:processed,
|
14
14
|
:rejected
|
15
|
-
|
16
|
-
def_delegators :@parser, :buffer
|
15
|
+
|
16
|
+
def_delegators :@parser, :buffer
|
17
17
|
def_delegator :clear_buffer, :buffer, :clear
|
18
18
|
def_delegator :clear_processed, :processed, :clear
|
19
19
|
def_delegator :clear_rejected, :rejected, :clear
|
20
20
|
def_delegator :clear_messages, :messages, :clear
|
21
21
|
|
22
22
|
# @param [Hash] options
|
23
|
+
# @option options [Symbol] :message_lib The name of a message library module eg MIDIMessage or Midilib
|
23
24
|
# @option options [Boolean] :timestamps Whether to report timestamps
|
24
25
|
def initialize(options = {})
|
25
26
|
@timestamps = options[:timestamps] || false
|
26
27
|
@callbacks, @processed, @rejected, @messages = [], [], [], []
|
27
|
-
@
|
28
|
+
@library = MessageLibrary.adapter(options[:message_lib])
|
29
|
+
@parser = Parser.new(@library)
|
28
30
|
end
|
29
|
-
|
31
|
+
|
30
32
|
# @return [Array<Object>]
|
31
33
|
def all_messages
|
32
34
|
@messages | @fragmented_messages
|
33
35
|
end
|
34
|
-
|
36
|
+
|
35
37
|
# The buffer as a single hex string
|
36
38
|
# @return [String]
|
37
39
|
def buffer_s
|
@@ -48,13 +50,13 @@ module Nibbler
|
|
48
50
|
def clear_messages
|
49
51
|
@messages.clear
|
50
52
|
end
|
51
|
-
|
53
|
+
|
52
54
|
# Convert messages to hashes with timestamps
|
53
55
|
def use_timestamps
|
54
56
|
if !@timestamps
|
55
57
|
@messages = @messages.map do |message|
|
56
|
-
{
|
57
|
-
:messages => message,
|
58
|
+
{
|
59
|
+
:messages => message,
|
58
60
|
:timestamp => nil
|
59
61
|
}
|
60
62
|
end
|
@@ -71,19 +73,19 @@ module Nibbler
|
|
71
73
|
options = args.last.kind_of?(Hash) ? args.pop : {}
|
72
74
|
timestamp = options[:timestamp]
|
73
75
|
|
74
|
-
use_timestamps if !timestamp.nil?
|
76
|
+
use_timestamps if !timestamp.nil?
|
75
77
|
|
76
78
|
result = process(args)
|
77
|
-
log(result, timestamp)
|
78
|
-
end
|
79
|
-
|
79
|
+
log(result, timestamp)
|
80
|
+
end
|
81
|
+
|
80
82
|
private
|
81
83
|
|
82
84
|
# Process the input
|
83
85
|
# @param [Array<Object>] input
|
84
86
|
# @return [Hash]
|
85
87
|
def process(input)
|
86
|
-
queue =
|
88
|
+
queue = DataProcessor.process(input)
|
87
89
|
@parser.process(queue)
|
88
90
|
end
|
89
91
|
|
@@ -96,16 +98,16 @@ module Nibbler
|
|
96
98
|
@rejected += parser_report[:rejected]
|
97
99
|
get_output(num)
|
98
100
|
end
|
99
|
-
|
101
|
+
|
100
102
|
# @param [Array<Object>] messages The MIDI messages to log
|
101
|
-
# @return [
|
103
|
+
# @return [Integer] The number of MIDI messages logged
|
102
104
|
def log_message(messages, options = {})
|
103
105
|
if @timestamps
|
104
106
|
messages_for_log = messages.count == 1 ? messages.first : messages
|
105
|
-
@messages << {
|
106
|
-
:messages => messages_for_log,
|
107
|
-
:timestamp => options[:timestamp]
|
108
|
-
}
|
107
|
+
@messages << {
|
108
|
+
:messages => messages_for_log,
|
109
|
+
:timestamp => options[:timestamp]
|
110
|
+
}
|
109
111
|
else
|
110
112
|
@messages += messages
|
111
113
|
end
|
@@ -122,13 +124,13 @@ module Nibbler
|
|
122
124
|
# 1 message: the message
|
123
125
|
# >1 message: an array of messages
|
124
126
|
#
|
125
|
-
# @param [
|
126
|
-
# @return [Array<Object>, Hash]
|
127
|
+
# @param [Integer] num The number of new messages to report
|
128
|
+
# @return [Array<Object>, Hash]
|
127
129
|
def get_output(num)
|
128
130
|
messages = @messages.last(num)
|
129
|
-
messages.count < 2 ? messages.first : messages
|
131
|
+
messages.count < 2 ? messages.first : messages
|
130
132
|
end
|
131
|
-
|
133
|
+
|
132
134
|
end
|
133
|
-
|
135
|
+
|
134
136
|
end
|
@@ -1,40 +1,76 @@
|
|
1
1
|
module Nibbler
|
2
|
-
|
2
|
+
|
3
3
|
# A helper for converting between different types of nibbles and bytes
|
4
4
|
module TypeConversion
|
5
5
|
|
6
6
|
extend self
|
7
|
-
|
7
|
+
|
8
8
|
# Converts an array of hex nibble strings to numeric bytes
|
9
|
+
# eg ["9", "0", "5", "0", "4", "0"] => [0x90, 0x50, 0x40]
|
9
10
|
# @param [Array<String>] nibbles
|
10
|
-
# @return [Array<
|
11
|
+
# @return [Array<Integer>]
|
11
12
|
def hex_chars_to_numeric_bytes(nibbles)
|
12
13
|
nibbles = nibbles.dup
|
13
14
|
# get rid of last nibble if there's an odd number
|
14
15
|
# it will be processed later anyway
|
15
16
|
nibbles.slice!(nibbles.length-2, 1) if nibbles.length.odd?
|
16
17
|
bytes = []
|
17
|
-
|
18
|
+
until (nibs = nibbles.slice!(0,2)).empty?
|
18
19
|
byte = (nibs[0].hex << 4) + nibs[1].hex
|
19
20
|
bytes << byte
|
20
21
|
end
|
21
22
|
bytes
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
# Converts a string of hex digits to string nibbles
|
26
|
+
# eg "905040" => ["9", "0", "5", "0", "4", "0"]
|
25
27
|
# @param [String] string
|
26
28
|
# @return [Array<String>]
|
27
29
|
def hex_str_to_hex_chars(string)
|
28
|
-
string.split(//)
|
30
|
+
string.split(//)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts a string of hex digits to numeric nibbles
|
34
|
+
# eg "905040" => [0x9, 0x0, 0x5, 0x0, 0x4, 0x0]
|
35
|
+
# @param [String] string
|
36
|
+
# @return [Array<String>]
|
37
|
+
def hex_str_to_numeric_nibbles(string)
|
38
|
+
bytes = hex_str_to_numeric_bytes(string)
|
39
|
+
numeric_bytes_to_numeric_nibbles(bytes)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts a string of hex digits to numeric bytes
|
43
|
+
# eg "905040" => [0x90, 0x50, 0x40]
|
44
|
+
# @param [String] string
|
45
|
+
# @return [Array<String>]
|
46
|
+
def hex_str_to_numeric_bytes(string)
|
47
|
+
chars = hex_str_to_hex_chars(string)
|
48
|
+
hex_chars_to_numeric_bytes(chars)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Converts an array bytes to an array of nibbles
|
52
|
+
# eg [0x90, 0x50, 0x40] => [0x9, 0x0, 0x5, 0x0, 0x4, 0x0]
|
53
|
+
# @param [String] string
|
54
|
+
# @return [Array<String>]
|
55
|
+
def numeric_bytes_to_numeric_nibbles(bytes)
|
56
|
+
bytes.map { |byte| numeric_byte_to_numeric_nibbles(byte) }.flatten
|
29
57
|
end
|
30
|
-
|
31
|
-
# Converts a numeric byte to an array of hex nibble strings
|
32
|
-
# @param [
|
58
|
+
|
59
|
+
# Converts a numeric byte to an array of hex nibble strings eg 0x90 => ["9", "0"]
|
60
|
+
# @param [Integer] num
|
33
61
|
# @return [Array<String>]
|
34
62
|
def numeric_byte_to_hex_chars(num)
|
35
|
-
|
63
|
+
nibbles = numeric_byte_to_numeric_nibbles(num)
|
64
|
+
nibbles.map { |n| n.to_s(16) }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Converts a numeric byte to an array of numeric nibbles eg 0x90 => [0x9, 0x0]
|
68
|
+
# @param [Integer] num
|
69
|
+
# @return [Array<String>]
|
70
|
+
def numeric_byte_to_numeric_nibbles(num)
|
71
|
+
[((num & 0xF0) >> 4), (num & 0x0F)]
|
36
72
|
end
|
37
73
|
|
38
74
|
end
|
39
|
-
|
75
|
+
|
40
76
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Nibbler::DataProcessorTest < Minitest::Test
|
4
|
+
|
5
|
+
context "DataProcessor" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@processor = Nibbler::DataProcessor
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#process" do
|
12
|
+
|
13
|
+
context "string" do
|
14
|
+
|
15
|
+
setup do
|
16
|
+
@str = "904050"
|
17
|
+
@nibbles = @processor.send(:process, @str)
|
18
|
+
end
|
19
|
+
|
20
|
+
should "not alter input" do
|
21
|
+
assert_equal("904050", @str)
|
22
|
+
end
|
23
|
+
|
24
|
+
should "return correct nibbles" do
|
25
|
+
assert_equal(["9", "0", "4", "0", "5", "0"], @nibbles)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context "numeric" do
|
31
|
+
|
32
|
+
setup do
|
33
|
+
@num = 0x90
|
34
|
+
@nibbles = @processor.send(:process, @num)
|
35
|
+
end
|
36
|
+
|
37
|
+
should "not alter input" do
|
38
|
+
assert_equal(0x90, @num)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "return correct nibbles" do
|
42
|
+
assert_equal(["9", "0"], @nibbles)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "mixed types" do
|
48
|
+
|
49
|
+
setup do
|
50
|
+
@array = [0x90, "90", "9"]
|
51
|
+
end
|
52
|
+
|
53
|
+
context "normal" do
|
54
|
+
|
55
|
+
setup do
|
56
|
+
@nibbles = @processor.send(:process, @array)
|
57
|
+
end
|
58
|
+
|
59
|
+
should "not alter input" do
|
60
|
+
assert_equal([0x90, "90", "9"], @array)
|
61
|
+
end
|
62
|
+
|
63
|
+
should "return correct nibbles" do
|
64
|
+
assert_equal(["9", "0", "9", "0", "9"], @nibbles)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
context "splatted" do
|
70
|
+
|
71
|
+
setup do
|
72
|
+
@nibbles = @processor.send(:process, *@array)
|
73
|
+
end
|
74
|
+
|
75
|
+
should "not alter input" do
|
76
|
+
assert_equal([0x90, "90", "9"], @array)
|
77
|
+
end
|
78
|
+
|
79
|
+
should "return correct nibbles" do
|
80
|
+
assert_equal(["9", "0", "9", "0", "9"], @nibbles)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context "#filter_numeric" do
|
90
|
+
|
91
|
+
context "filtered" do
|
92
|
+
|
93
|
+
setup do
|
94
|
+
@num = 560
|
95
|
+
@result = @processor.send(:filter_numeric, @num)
|
96
|
+
end
|
97
|
+
|
98
|
+
should "not alter input" do
|
99
|
+
assert_equal(560, @num)
|
100
|
+
end
|
101
|
+
|
102
|
+
should "return nil" do
|
103
|
+
assert_nil @result
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
context "passing" do
|
109
|
+
|
110
|
+
setup do
|
111
|
+
@num = 50
|
112
|
+
@result = @processor.send(:filter_numeric, @num)
|
113
|
+
end
|
114
|
+
|
115
|
+
should "not alter input" do
|
116
|
+
assert_equal(50, @num)
|
117
|
+
end
|
118
|
+
|
119
|
+
should "return number" do
|
120
|
+
assert_equal(50, @result)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context "#filter_string" do
|
128
|
+
|
129
|
+
setup do
|
130
|
+
@input = "(0xAdjskla#(#"
|
131
|
+
@result = @processor.send(:filter_string, @input)
|
132
|
+
end
|
133
|
+
|
134
|
+
should "not alter input" do
|
135
|
+
assert_equal("(0xAdjskla#(#", @input)
|
136
|
+
end
|
137
|
+
|
138
|
+
should "return valid chars" do
|
139
|
+
assert_equal("0ADA", @result)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Nibbler::FunctionalBufferTest < Minitest::Test
|
4
|
+
|
5
|
+
context "Parser::Buffer" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@nibbler = Nibbler.new
|
9
|
+
end
|
10
|
+
|
11
|
+
should "have processed string nibble" do
|
12
|
+
@nibbler.parse("9")
|
13
|
+
assert_equal(["9"], @nibbler.buffer)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "have processed numeric byte" do
|
17
|
+
@nibbler.parse(0x90)
|
18
|
+
assert_equal(["9", "0"], @nibbler.buffer)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "have processed string byte" do
|
22
|
+
@nibbler.parse("90")
|
23
|
+
assert_equal(["9", "0"], @nibbler.buffer)
|
24
|
+
end
|
25
|
+
|
26
|
+
should "have processed array" do
|
27
|
+
@nibbler.parse([0x90])
|
28
|
+
assert_equal(["9", "0"], @nibbler.buffer)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "have processed numeric bytes" do
|
32
|
+
@nibbler.parse(0x90, 0x40)
|
33
|
+
assert_equal(["9", "0", "4", "0"], @nibbler.buffer)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "have processed string bytes" do
|
37
|
+
@nibbler.parse("90", "40")
|
38
|
+
assert_equal(["9", "0", "4", "0"], @nibbler.buffer)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "have processed two-byte string" do
|
42
|
+
@nibbler.parse("9040")
|
43
|
+
assert_equal(["9", "0", "4", "0"], @nibbler.buffer)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "have processed mixed bytes" do
|
47
|
+
@nibbler.parse("90", 0x40)
|
48
|
+
assert_equal(["9", "0", "4", "0"], @nibbler.buffer)
|
49
|
+
end
|
50
|
+
|
51
|
+
should "have processed mixed nibble and byte" do
|
52
|
+
@nibbler.parse("9", 0x40)
|
53
|
+
assert_equal(["9", "4", "0"], @nibbler.buffer)
|
54
|
+
end
|
55
|
+
|
56
|
+
should "have processed separate data" do
|
57
|
+
@nibbler.parse("9")
|
58
|
+
@nibbler.parse(0x40)
|
59
|
+
assert_equal(["9", "4", "0"], @nibbler.buffer)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Nibbler::FunctionalRejectedTest < Minitest::Test
|
4
|
+
|
5
|
+
context "Rejected" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@nibbler = Nibbler.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "leading chars" do
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@message = @nibbler.parse("0", "9", "04", "040")
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return correct message" do
|
18
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "reject extra char" do
|
22
|
+
refute_empty @nibbler.rejected
|
23
|
+
assert_equal("0", @nibbler.rejected.first)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
context "2 leading chars" do
|
29
|
+
|
30
|
+
setup do
|
31
|
+
@message = @nibbler.parse("1", "0", "9", "04", "040")
|
32
|
+
end
|
33
|
+
|
34
|
+
should "reject two leading chars" do
|
35
|
+
refute_empty @nibbler.rejected
|
36
|
+
assert_equal "1", @nibbler.rejected[0]
|
37
|
+
assert_equal "0", @nibbler.rejected[1]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context "leading string" do
|
43
|
+
|
44
|
+
setup do
|
45
|
+
@message = @nibbler.parse("10", "9", "04", "040")
|
46
|
+
end
|
47
|
+
|
48
|
+
should "return correct message" do
|
49
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "reject chars in leading string" do
|
53
|
+
refute_empty @nibbler.rejected
|
54
|
+
assert_equal "1", @nibbler.rejected[0]
|
55
|
+
assert_equal "0", @nibbler.rejected[1]
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context "long leading string" do
|
61
|
+
|
62
|
+
setup do
|
63
|
+
@message = @nibbler.parse("000001000010", "9", "04", "040")
|
64
|
+
end
|
65
|
+
|
66
|
+
should "return correct message" do
|
67
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "return string" do
|
71
|
+
refute_empty @nibbler.rejected
|
72
|
+
assert_equal("000001000010".split(//), @nibbler.rejected)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "long leading string overlap" do
|
78
|
+
|
79
|
+
setup do
|
80
|
+
@message = @nibbler.parse("000001000010090", "4", "040")
|
81
|
+
end
|
82
|
+
|
83
|
+
should "return correct message" do
|
84
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
85
|
+
end
|
86
|
+
|
87
|
+
should "return leading string" do
|
88
|
+
refute_empty @nibbler.rejected
|
89
|
+
assert_equal("0000010000100".split(//), @nibbler.rejected)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
context "leading number" do
|
95
|
+
|
96
|
+
setup do
|
97
|
+
@message = @nibbler.parse(0x30, "9", "04", "040")
|
98
|
+
end
|
99
|
+
|
100
|
+
should "return correct message" do
|
101
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
102
|
+
end
|
103
|
+
|
104
|
+
should "return leading numbers" do
|
105
|
+
refute_empty @nibbler.rejected
|
106
|
+
assert_equal "3", @nibbler.rejected[0]
|
107
|
+
assert_equal "0", @nibbler.rejected[1]
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context "2 leading numbers" do
|
113
|
+
|
114
|
+
setup do
|
115
|
+
@message = @nibbler.parse(0x60, 0x30, "9", "04", "040")
|
116
|
+
end
|
117
|
+
|
118
|
+
should "return correct message" do
|
119
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
120
|
+
end
|
121
|
+
|
122
|
+
should "return leading numbers" do
|
123
|
+
refute_empty @nibbler.rejected
|
124
|
+
assert_equal "6", @nibbler.rejected[0]
|
125
|
+
assert_equal "0", @nibbler.rejected[1]
|
126
|
+
assert_equal "3", @nibbler.rejected[2]
|
127
|
+
assert_equal "0", @nibbler.rejected[3]
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context "3 leading numbers" do
|
133
|
+
|
134
|
+
setup do
|
135
|
+
@message = @nibbler.parse(0x00, 0x30, "9", "04", "040")
|
136
|
+
end
|
137
|
+
|
138
|
+
should "return correct message" do
|
139
|
+
assert_equal(::MIDIMessage::NoteOn, @message.class)
|
140
|
+
end
|
141
|
+
|
142
|
+
should "return leading numbers" do
|
143
|
+
refute_empty @nibbler.rejected
|
144
|
+
assert_equal "0", @nibbler.rejected[0]
|
145
|
+
assert_equal "0", @nibbler.rejected[1]
|
146
|
+
assert_equal "3", @nibbler.rejected[2]
|
147
|
+
assert_equal "0", @nibbler.rejected[3]
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|