midi-nibbler 0.1.1 → 0.2.1

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.
@@ -0,0 +1,134 @@
1
+ module Nibbler
2
+
3
+ # A parser session
4
+ #
5
+ # Holds on to data that is not relevant to the parser between calls. For instance,
6
+ # past messages, rejected bytes
7
+ #
8
+ class Session
9
+
10
+ extend Forwardable
11
+
12
+ attr_reader :messages,
13
+ :processed,
14
+ :rejected
15
+
16
+ def_delegators :@parser, :buffer
17
+ def_delegator :clear_buffer, :buffer, :clear
18
+ def_delegator :clear_processed, :processed, :clear
19
+ def_delegator :clear_rejected, :rejected, :clear
20
+ def_delegator :clear_messages, :messages, :clear
21
+
22
+ # @param [Hash] options
23
+ # @option options [Boolean] :timestamps Whether to report timestamps
24
+ def initialize(options = {})
25
+ @timestamps = options[:timestamps] || false
26
+ @callbacks, @processed, @rejected, @messages = [], [], [], []
27
+ @parser = Parser.new(options)
28
+ end
29
+
30
+ # @return [Array<Object>]
31
+ def all_messages
32
+ @messages | @fragmented_messages
33
+ end
34
+
35
+ # The buffer as a single hex string
36
+ # @return [String]
37
+ def buffer_s
38
+ buffer.join
39
+ end
40
+ alias_method :buffer_hex, :buffer_s
41
+
42
+ # Clear the parser buffer
43
+ def clear_buffer
44
+ buffer.clear
45
+ end
46
+
47
+ # Clear the message log
48
+ def clear_messages
49
+ @messages.clear
50
+ end
51
+
52
+ # Convert messages to hashes with timestamps
53
+ def use_timestamps
54
+ if !@timestamps
55
+ @messages = @messages.map do |message|
56
+ {
57
+ :messages => message,
58
+ :timestamp => nil
59
+ }
60
+ end
61
+ @timestamps = true
62
+ end
63
+ end
64
+
65
+ # Parse some input
66
+ # @param [*Object] args
67
+ # @param [Hash] options (can be included as the last arg)
68
+ # @option options [Time] :timestamp A timestamp to store with the messages that result
69
+ # @return [Array<Object>, Hash]
70
+ def parse(*args)
71
+ options = args.last.kind_of?(Hash) ? args.pop : {}
72
+ timestamp = options[:timestamp]
73
+
74
+ use_timestamps if !timestamp.nil?
75
+
76
+ result = process(args)
77
+ log(result, timestamp)
78
+ end
79
+
80
+ private
81
+
82
+ # Process the input
83
+ # @param [Array<Object>] input
84
+ # @return [Hash]
85
+ def process(input)
86
+ queue = HexProcessor.process(input)
87
+ @parser.process(queue)
88
+ end
89
+
90
+ # @param [Hash] parser_report
91
+ # @param [Time] timestamp
92
+ # @return [Array<Object>, Hash]
93
+ def log(parser_report, timestamp)
94
+ num = log_message(parser_report[:messages], :timestamp => timestamp)
95
+ @processed += parser_report[:processed]
96
+ @rejected += parser_report[:rejected]
97
+ get_output(num)
98
+ end
99
+
100
+ # @param [Array<Object>] messages The MIDI messages to log
101
+ # @return [Fixnum] The number of MIDI messages logged
102
+ def log_message(messages, options = {})
103
+ if @timestamps
104
+ messages_for_log = messages.count == 1 ? messages.first : messages
105
+ @messages << {
106
+ :messages => messages_for_log,
107
+ :timestamp => options[:timestamp]
108
+ }
109
+ else
110
+ @messages += messages
111
+ end
112
+ messages.count
113
+ end
114
+
115
+ # A report on the given number of most recent messages
116
+ #
117
+ # If timestamps are being used, will be a hash of messages and timestamp,
118
+ # otherwise just the messages
119
+ #
120
+ # The messages type will vary depending on the number of messages that were parsed:
121
+ # 0 messages: nil
122
+ # 1 message: the message
123
+ # >1 message: an array of messages
124
+ #
125
+ # @param [Fixnum] num The number of new messages to report
126
+ # @return [Array<Object>, Hash]
127
+ def get_output(num)
128
+ messages = @messages.last(num)
129
+ messages.count < 2 ? messages.first : messages
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -1,11 +1,14 @@
1
- #!/usr/bin/env ruby
2
- #
3
1
  module Nibbler
4
2
 
5
- # this is a helper for converting nibbles and bytes
3
+ # A helper for converting between different types of nibbles and bytes
6
4
  module TypeConversion
5
+
6
+ extend self
7
7
 
8
- def self.hex_chars_to_numeric_bytes(nibbles)
8
+ # Converts an array of hex nibble strings to numeric bytes
9
+ # @param [Array<String>] nibbles
10
+ # @return [Array<Fixnum>]
11
+ def hex_chars_to_numeric_bytes(nibbles)
9
12
  nibbles = nibbles.dup
10
13
  # get rid of last nibble if there's an odd number
11
14
  # it will be processed later anyway
@@ -18,16 +21,20 @@ module Nibbler
18
21
  bytes
19
22
  end
20
23
 
21
- # converts a string of hex digits to bytes
22
- def self.hex_str_to_hex_chars(str)
23
- str.split(//)
24
+ # Converts a string of hex digits to string nibbles
25
+ # @param [String] string
26
+ # @return [Array<String>]
27
+ def hex_str_to_hex_chars(string)
28
+ string.split(//)
24
29
  end
25
30
 
26
- def self.numeric_byte_to_hex_chars(num)
31
+ # Converts a numeric byte to an array of hex nibble strings
32
+ # @param [Fixnum] num
33
+ # @return [Array<String>]
34
+ def numeric_byte_to_hex_chars(num)
27
35
  [((num & 0xF0) >> 4), (num & 0x0F)].map { |n| n.to_s(16) }
28
36
  end
29
37
 
30
-
31
38
  end
32
39
 
33
- end
40
+ end
data/test/helper.rb CHANGED
@@ -1,11 +1,5 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  dir = File.dirname(File.expand_path(__FILE__))
4
- $LOAD_PATH.unshift dir + '/../lib'
5
-
6
- require 'test/unit'
7
- require 'nibbler'
2
+ $LOAD_PATH.unshift dir + "/../lib"
8
3
 
9
- module TestHelper
10
-
11
- end
4
+ require "test/unit"
5
+ require "nibbler"
@@ -1,61 +1,56 @@
1
- #!/usr/bin/env ruby
1
+ require "helper"
2
2
 
3
- require 'helper'
4
-
5
- class HexCharArrayFilterTest < Test::Unit::TestCase
6
-
7
- include Nibbler
8
- include TestHelper
3
+ class Nibbler::HexProcessorTest < Test::Unit::TestCase
9
4
 
10
5
  def test_to_nibbles_array_mixed
11
- filter = HexCharArrayFilter.new
6
+ processor = Nibbler::HexProcessor
12
7
  array = [0x90, "90", "9"]
13
- nibbles = filter.send(:process, array)
8
+ nibbles = processor.send(:process, array)
14
9
  assert_equal([0x90, "90", "9"], array)
15
10
  assert_equal(["9", "0", "9", "0", "9"], nibbles)
16
11
  end
17
12
 
18
13
  def test_to_nibbles_mixed
19
- filter = HexCharArrayFilter.new
14
+ processor = Nibbler::HexProcessor
20
15
  array = [0x90, "90", "9"]
21
- nibbles = filter.send(:process, *array)
16
+ nibbles = processor.send(:process, *array)
22
17
  assert_equal([0x90, "90", "9"], array)
23
18
  assert_equal(["9", "0", "9", "0", "9"], nibbles)
24
19
  end
25
20
 
26
21
  def test_to_nibbles_numeric
27
- filter = HexCharArrayFilter.new
22
+ processor = Nibbler::HexProcessor
28
23
  num = 0x90
29
- nibbles = filter.send(:process, num)
24
+ nibbles = processor.send(:process, num)
30
25
  assert_equal(0x90, num)
31
26
  assert_equal(["9", "0"], nibbles)
32
27
  end
33
28
 
34
29
  def test_to_nibbles_string
35
- filter = HexCharArrayFilter.new
30
+ processor = Nibbler::HexProcessor
36
31
  str = "904050"
37
- nibbles = filter.send(:process, str)
32
+ nibbles = processor.send(:process, str)
38
33
  assert_equal("904050", str)
39
34
  assert_equal(["9", "0", "4", "0", "5", "0"], nibbles)
40
35
  end
41
36
 
42
- def test_filter_numeric
43
- filter = HexCharArrayFilter.new
37
+ def test_processor_numeric
38
+ processor = Nibbler::HexProcessor
44
39
  badnum = 560
45
- output = filter.send(:filter_numeric, badnum)
40
+ output = processor.send(:filter_numeric, badnum)
46
41
  assert_equal(560, badnum)
47
42
  assert_equal(nil, output)
48
43
  goodnum = 50
49
- output = filter.send(:filter_numeric, goodnum)
44
+ output = processor.send(:filter_numeric, goodnum)
50
45
  assert_equal(50, goodnum)
51
46
  assert_equal(50, output)
52
47
  end
53
48
 
54
- def test_filter_string
55
- filter = HexCharArrayFilter.new
49
+ def test_processor_string
50
+ processor = Nibbler::HexProcessor
56
51
  str = "(0xAdjskla#(#"
57
- outp = filter.send(:filter_string, str)
52
+ outp = processor.send(:filter_string, str)
58
53
  assert_equal("0ADA", outp)
59
54
  end
60
55
 
61
- end
56
+ end
@@ -0,0 +1,126 @@
1
+ require "helper"
2
+ require "nibbler/midi-message"
3
+
4
+ class Nibbler::MIDIMessageTest < Test::Unit::TestCase
5
+
6
+ def test_note_off
7
+ lib = Nibbler::MIDIMessage
8
+ message = lib.note_off(0, 0x40, 0x40)
9
+ assert_equal(MIDIMessage::NoteOff, message.class)
10
+ assert_equal(0, message.channel)
11
+ assert_equal(0x40, message.note)
12
+ assert_equal(0x40, message.velocity)
13
+ end
14
+
15
+ def test_note_on
16
+ lib = Nibbler::MIDIMessage
17
+ message = lib.note_on(0x0, 0x40, 0x40)
18
+ assert_equal(MIDIMessage::NoteOn, message.class)
19
+ assert_equal(0, message.channel)
20
+ assert_equal(0x40, message.note)
21
+ assert_equal(0x40, message.velocity)
22
+ end
23
+
24
+ def test_polyphonic_aftertouch
25
+ lib = Nibbler::MIDIMessage
26
+ message = lib.polyphonic_aftertouch(0x1, 0x40, 0x40)
27
+ assert_equal(MIDIMessage::PolyphonicAftertouch, message.class)
28
+ assert_equal(1, message.channel)
29
+ assert_equal(0x40, message.note)
30
+ assert_equal(0x40, message.value)
31
+ end
32
+
33
+ def test_control_change
34
+ lib = Nibbler::MIDIMessage
35
+ message = lib.control_change(0x2, 0x20, 0x20)
36
+ assert_equal(MIDIMessage::ControlChange, message.class)
37
+ assert_equal(message.channel, 2)
38
+ assert_equal(0x20, message.index)
39
+ assert_equal(0x20, message.value)
40
+ end
41
+
42
+ def test_program_change
43
+ lib = Nibbler::MIDIMessage
44
+ message = lib.program_change(0x3, 0x40)
45
+ assert_equal(MIDIMessage::ProgramChange, message.class)
46
+ assert_equal(3, message.channel)
47
+ assert_equal(0x40, message.program)
48
+ end
49
+
50
+ def test_channel_aftertouch
51
+ lib = Nibbler::MIDIMessage
52
+ message = lib.channel_aftertouch(0x3, 0x50)
53
+ assert_equal(MIDIMessage::ChannelAftertouch, message.class)
54
+ assert_equal(3, message.channel)
55
+ assert_equal(0x50, message.value)
56
+ end
57
+
58
+ def test_pitch_bend
59
+ lib = Nibbler::MIDIMessage
60
+ message = lib.pitch_bend(0x0, 0x20, 0x00) # center
61
+ assert_equal(MIDIMessage::PitchBend, message.class)
62
+ assert_equal(0, message.channel)
63
+ assert_equal(0x20, message.low)
64
+ assert_equal(0x00, message.high)
65
+ end
66
+
67
+ def test_system_exclusive_command
68
+ lib = Nibbler::MIDIMessage
69
+ message = lib.system_exclusive(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
70
+ assert_equal(MIDIMessage::SystemExclusive::Command, message.class)
71
+ assert_equal([0xF0, [0x41, 0x10, 0x42], 0x12, [0x40, 0x00, 0x7F], [0x00], 0x41, 0xF7], message.to_a)
72
+ assert_equal([0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], message.to_bytes)
73
+ assert_equal("F04110421240007F0041F7", message.to_hex_s)
74
+ end
75
+
76
+ def test_system_exclusive_request
77
+ lib = Nibbler::MIDIMessage
78
+ message = lib.system_exclusive(0xF0, 0x41, 0x10, 0x42, 0x11, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
79
+ assert_equal(MIDIMessage::SystemExclusive::Request, message.class)
80
+ assert_equal([0xF0, [0x41, 0x10, 0x42], 0x11, [0x40, 0x00, 0x7F], [0x00, 0x00, 0x00], 0x41, 0xF7], message.to_a)
81
+ assert_equal([0xF0, 0x41, 0x10, 0x42, 0x11, 0x40, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x41, 0xF7], message.to_bytes)
82
+ assert_equal("F04110421140007F00000041F7", message.to_hex_s)
83
+ end
84
+
85
+ def test_system_exclusive_node
86
+ lib = Nibbler::MIDIMessage
87
+ message = lib.system_exclusive(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
88
+ node = message.node
89
+ assert_equal(MIDIMessage::SystemExclusive::Node, node.class)
90
+ assert_equal(0x41, node.manufacturer_id)
91
+ assert_equal(0x42, node.model_id)
92
+ assert_equal(0x10, node.device_id)
93
+ end
94
+
95
+ def test_system_common_generic_3_bytes
96
+ lib = Nibbler::MIDIMessage
97
+ message = lib.system_common(0x1, 0x50, 0xA0)
98
+ assert_equal(MIDIMessage::SystemCommon, message.class)
99
+ assert_equal(1, message.status[1])
100
+ assert_equal(0x50, message.data[0])
101
+ assert_equal(0xA0, message.data[1])
102
+ end
103
+
104
+ def test_system_common_generic_2_bytes
105
+ nibbler = Nibbler.new
106
+ message = nibbler.parse(0xF1, 0x50)
107
+ assert_equal(MIDIMessage::SystemCommon, message.class)
108
+ assert_equal(1, message.status[1])
109
+ assert_equal(0x50, message.data[0])
110
+ end
111
+
112
+ def test_system_common_generic_1_byte
113
+ nibbler = Nibbler.new
114
+ message = nibbler.parse(0xF1)
115
+ assert_equal(MIDIMessage::SystemCommon, message.class)
116
+ assert_equal(1, message.status[1])
117
+ end
118
+
119
+ def test_system_realtime
120
+ nibbler = Nibbler.new
121
+ message = nibbler.parse(0xF8)
122
+ assert_equal(MIDIMessage::SystemRealtime, message.class)
123
+ assert_equal(8, message.id)
124
+ end
125
+
126
+ end
@@ -0,0 +1,131 @@
1
+ require "helper"
2
+ require "nibbler/midilib"
3
+
4
+ class Nibbler::MidilibTest < Test::Unit::TestCase
5
+
6
+ def test_note_off
7
+ lib = Nibbler::Midilib
8
+ message = lib.note_off(0x0, 0x40, 0x40)
9
+ assert_equal(MIDI::NoteOff, message.class)
10
+ assert_equal(0, message.channel)
11
+ assert_equal(0x40, message.note)
12
+ assert_equal(0x40, message.velocity)
13
+ end
14
+
15
+ def test_note_on
16
+ lib = Nibbler::Midilib
17
+ message = lib.note_on(0x0, 0x40, 0x40)
18
+ assert_equal(MIDI::NoteOn, message.class)
19
+ assert_equal(0, message.channel)
20
+ assert_equal(0x40, message.note)
21
+ assert_equal(0x40, message.velocity)
22
+ end
23
+
24
+ def test_polyphonic_aftertouch
25
+ lib = Nibbler::Midilib
26
+ message = lib.polyphonic_aftertouch(0x1, 0x40, 0x40)
27
+ assert_equal(MIDI::PolyPressure, message.class)
28
+ assert_equal(1, message.channel)
29
+ assert_equal(0x40, message.note)
30
+ assert_equal(0x40, message.pressure)
31
+ end
32
+
33
+ def test_control_change
34
+ lib = Nibbler::Midilib
35
+ message = lib.control_change(0x2, 0x20, 0x20)
36
+ assert_equal(MIDI::Controller, message.class)
37
+ assert_equal(message.channel, 2)
38
+ assert_equal(0x20, message.controller)
39
+ assert_equal(0x20, message.value)
40
+ end
41
+
42
+ def test_program_change
43
+ lib = Nibbler::Midilib
44
+ message = lib.program_change(0x3, 0x40)
45
+ assert_equal(MIDI::ProgramChange, message.class)
46
+ assert_equal(3, message.channel)
47
+ assert_equal(0x40, message.program)
48
+ end
49
+
50
+ def test_channel_aftertouch
51
+ lib = Nibbler::Midilib
52
+ message = lib.channel_aftertouch(0x3, 0x50)
53
+ assert_equal(MIDI::ChannelPressure, message.class)
54
+ assert_equal(3, message.channel)
55
+ assert_equal(0x50, message.pressure)
56
+ end
57
+
58
+ def test_pitch_bend
59
+ # to-do handle the midilib lsb/msb
60
+ # right now the second data byte is being thrown away
61
+ lib = Nibbler::Midilib
62
+ message = lib.pitch_bend(0x0, 0x20, 0x00)
63
+ assert_equal(MIDI::PitchBend, message.class)
64
+ assert_equal(0, message.channel)
65
+ assert_equal(0x20, message.value)
66
+ end
67
+
68
+ def test_system_exclusive
69
+ lib = Nibbler::Midilib
70
+ message = lib.system_exclusive(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
71
+ assert_equal(MIDI::SystemExclusive, message.class)
72
+ assert_equal([0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7], message.data)
73
+ end
74
+
75
+ def test_song_pointer
76
+ lib = Nibbler::Midilib
77
+ message = lib.system_common(0x2, 0xF0)
78
+ assert_equal(MIDI::SongPointer, message.class)
79
+ assert_equal(0xF0, message.pointer)
80
+ end
81
+
82
+ def test_song_select
83
+ lib = Nibbler::Midilib
84
+ message = lib.system_common(0x3, 0xA0)
85
+ assert_equal(MIDI::SongSelect, message.class)
86
+ assert_equal(0xA0, message.song)
87
+ end
88
+
89
+ def test_tune_request
90
+ lib = Nibbler::Midilib
91
+ message = lib.system_common(0x6)
92
+ assert_equal(MIDI::TuneRequest, message.class)
93
+ end
94
+
95
+ def test_clock
96
+ lib = Nibbler::Midilib
97
+ message = lib.system_realtime(0x8)
98
+ assert_equal(MIDI::Clock, message.class)
99
+ end
100
+
101
+ def test_start
102
+ lib = Nibbler::Midilib
103
+ message = lib.system_realtime(0xA)
104
+ assert_equal(MIDI::Start, message.class)
105
+ end
106
+
107
+ def test_continue
108
+ lib = Nibbler::Midilib
109
+ message = lib.system_realtime(0xB)
110
+ assert_equal(MIDI::Continue, message.class)
111
+ end
112
+
113
+ def test_stop
114
+ lib = Nibbler::Midilib
115
+ message = lib.system_realtime(0xC)
116
+ assert_equal(MIDI::Stop, message.class)
117
+ end
118
+
119
+ def test_sense
120
+ lib = Nibbler::Midilib
121
+ message = lib.system_realtime(0xE)
122
+ assert_equal(MIDI::ActiveSense, message.class)
123
+ end
124
+
125
+ def test_reset
126
+ lib = Nibbler::Midilib
127
+ message = lib.system_realtime(0xF)
128
+ assert_equal(MIDI::SystemReset, message.class)
129
+ end
130
+
131
+ end