midi-nibbler 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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