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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e8413384db29c1e35d128afb0d982cfd866472c8
4
+ data.tar.gz: 1652fbc29119c0d0cb0f97f4e7c66167cf906253
5
+ SHA512:
6
+ metadata.gz: 66610505ed6b6111864ca1467d90ab72366bdb874ec3f401b56608e857ba2139b7fe3fe8144fee7bceafe5ef4c7eb6ce16e4f931399fccb998cc3682bfb58f52
7
+ data.tar.gz: 228e832b552cac66f79b46d2ea9cd6bf1edd67d7535e791e00ec4621a1a74180f6f365ee661268c87936379e06c370abd7adeccc2feefa54438b401eaf6b08a2
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2010-2011 Ari Russo
1
+ Copyright 2011-2014 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.
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
10
10
  distributed under the License is distributed on an "AS IS" BASIS,
11
11
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  See the License for the specific language governing permissions and
13
- limitations under the License.
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Nibbler
2
+
3
+ ![nibbler](http://img17.imageshack.us/img17/1713/dogwithsynth.jpg)
4
+
5
+ Parse MIDI Messages
6
+
7
+ ## Install
8
+
9
+ `gem install midi-nibbler`
10
+
11
+ or using Bundler, add this to your Gemfile
12
+
13
+ `gem "midi-nibbler"`
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ require 'nibbler'
19
+
20
+ nibbler = Nibbler.new
21
+ ```
22
+
23
+ Enter a message piece by piece
24
+
25
+ ```ruby
26
+ nibbler.parse("90")
27
+ => nil
28
+
29
+ nibbler.parse("40")
30
+ => nil
31
+
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,
40
+ @verbose_name="Note On: C3">
41
+ ```
42
+
43
+ Enter a message all at once
44
+
45
+ ```ruby
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,
55
+ @verbose_name="Note On: C3">
56
+ ```
57
+
58
+ Use bytes
59
+
60
+ ```ruby
61
+ nibbler.parse(0x90, 0x40, 0x40)
62
+ => #<MIDIMessage::NoteOn:0x98c9818 ...>
63
+ ```
64
+
65
+ You can use nibbles in string format
66
+
67
+ ```ruby
68
+ nibbler.parse("9", "0", "4", "0", "4", "0")
69
+ => #<MIDIMessage::NoteOn:0x98c9818 ...>
70
+ ```
71
+
72
+ Interchange the different types
73
+
74
+ ```ruby
75
+ nibbler.parse("9", "0", 0x40, 64)
76
+ => #<MIDIMessage::NoteOn:0x98c9818 ...>
77
+ ```
78
+
79
+ Use running status
80
+
81
+ ```ruby
82
+ nibbler.parse(0x40, 64)
83
+ => #<MIDIMessage::NoteOn:0x98c9818 ...>
84
+ ```
85
+
86
+ Look at the messages we've parsed
87
+
88
+ ```ruby
89
+ nibbler.messages
90
+ => [#<MIDIMessage::NoteOn:0x98c9804 ...>
91
+ #<MIDIMessage::NoteOn:0x98c9811 ...>]
92
+ ```
93
+
94
+ Add an incomplete message
95
+
96
+ ```ruby
97
+ nibbler.parse("9")
98
+ nibbler.parse("40")
99
+ ```
100
+
101
+ See progress
102
+
103
+ ```ruby
104
+ nibbler.buffer
105
+ => ["9", "4", "0"]
106
+
107
+ nibbler.buffer_s
108
+ => "940"
109
+ ```
110
+
111
+ Pass in a timestamp
112
+
113
+ ```ruby
114
+ nibbler.parse("904040", :timestamp => Time.now.to_i)
115
+ => { :messages=> #<MIDIMessage::NoteOn:0x92f4564 ..>, :timestamp=>1304488440 }
116
+ ```
117
+
118
+ Nibbler defaults to generate [midi-message](http://github.com/arirusso/midi-message) objects, but it is also possible to use [midilib](https://github.com/jimm/midilib)
119
+
120
+ ```ruby
121
+ Nibbler.new(:message_lib => :midilib)
122
+
123
+ nibbler.parse("9", "0", 0x40, "40")
124
+ => "0: ch 00 on 40 40"
125
+ ```
126
+
127
+ ## Also see
128
+
129
+ * [midi-eye](http://github.com/arirusso/midi-eye), a MIDI event listener based on nibbler
130
+
131
+ ## Author
132
+
133
+ * [Ari Russo](http://github.com/arirusso) <ari.russo at gmail.com>
134
+
135
+ ## License
136
+
137
+ Apache 2.0, See the file LICENSE
138
+
139
+ Copyright (c) 2011-2014 Ari Russo
data/lib/nibbler.rb CHANGED
@@ -1,26 +1,29 @@
1
- #!/usr/bin/env ruby
2
1
  #
3
2
  # Parse MIDI Messages
4
- # (c)2011 Ari Russo and licensed under the Apache 2.0 License
3
+ # (c)2011-2014 Ari Russo and licensed under the Apache 2.0 License
5
4
  #
6
5
 
7
- require 'forwardable'
6
+ # libs
7
+ require "forwardable"
8
8
 
9
- require 'nibbler/nibbler'
10
- require 'nibbler/parser'
11
- require 'nibbler/type_conversion'
12
- require 'nibbler/hex_char_array_filter'
9
+ # modules
10
+ require "nibbler/hex_processor"
11
+ require "nibbler/type_conversion"
12
+
13
+ # classes
14
+ require "nibbler/parser"
15
+ require "nibbler/session"
13
16
 
14
17
  #
15
18
  # Parse MIDI Messages
16
19
  #
17
20
  module Nibbler
18
21
 
19
- VERSION = "0.1.1"
22
+ VERSION = "0.2.1"
20
23
 
21
- # shortcut to Parser.new
24
+ # Shortcut to a new parser session
22
25
  def self.new(*a, &block)
23
- Nibbler.new(*a, &block)
26
+ Session.new(*a, &block)
24
27
  end
25
28
 
26
29
  end
@@ -0,0 +1,47 @@
1
+ module Nibbler
2
+
3
+ # Accepts various types of input and returns an array of hex digit chars
4
+ module HexProcessor
5
+
6
+ extend self
7
+
8
+ # Accepts various types of input and returns an array of hex digit chars
9
+ # Invalid input is disregarded
10
+ #
11
+ # @param [*String, *Fixnum] args
12
+ # @return [Array<String>] An array of hex string nibbles eg "6", "a"
13
+ def process(*args)
14
+ args.map { |arg| convert(arg) }.flatten.compact.map(&:upcase)
15
+ end
16
+
17
+ private
18
+
19
+ # Convert a single value to hex chars
20
+ # @param [Array<Fixnum>, Array<String>, Fixnum, String] value
21
+ # @return [Array<String>]
22
+ def convert(value)
23
+ case value
24
+ when Array then value.map { |arr| process(*arr) }.reduce(:+)
25
+ 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))
27
+ end
28
+ end
29
+
30
+ # Limit the given number to bytes usable in MIDI ie values (0..240)
31
+ # returns nil if the byte is outside of that range
32
+ # @param [Fixnum] num
33
+ # @return [Fixnum, nil]
34
+ def filter_numeric(num)
35
+ num if (0x00..0xFF).include?(num)
36
+ end
37
+
38
+ # Only return valid hex string characters
39
+ # @param [String] string
40
+ # @return [String]
41
+ def filter_string(string)
42
+ string.gsub(/[^0-9a-fA-F]/, "").upcase
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,53 @@
1
+ require "midi-message"
2
+
3
+ module Nibbler
4
+
5
+ # Construct messages with MIDIMessage in a generic way
6
+ # http://github.com/arirusso/midi-message
7
+ module MIDIMessage
8
+
9
+ extend self
10
+
11
+ def note_off(second_nibble, data_byte_1, data_byte_2)
12
+ ::MIDIMessage::NoteOff.new(second_nibble, data_byte_1, data_byte_2)
13
+ end
14
+
15
+ def note_on(second_nibble, data_byte_1, data_byte_2)
16
+ ::MIDIMessage::NoteOn.new(second_nibble, data_byte_1, data_byte_2)
17
+ end
18
+
19
+ def polyphonic_aftertouch(second_nibble, data_byte_1, data_byte_2)
20
+ ::MIDIMessage::PolyphonicAftertouch.new(second_nibble, data_byte_1, data_byte_2)
21
+ end
22
+
23
+ def control_change(second_nibble, data_byte_1, data_byte_2)
24
+ ::MIDIMessage::ControlChange.new(second_nibble, data_byte_1, data_byte_2)
25
+ end
26
+
27
+ def program_change(second_nibble, data_byte)
28
+ ::MIDIMessage::ProgramChange.new(second_nibble, data_byte)
29
+ end
30
+
31
+ def channel_aftertouch(second_nibble, data_byte)
32
+ ::MIDIMessage::ChannelAftertouch.new(second_nibble, data_byte)
33
+ end
34
+
35
+ def pitch_bend(second_nibble, data_byte_1, data_byte_2)
36
+ ::MIDIMessage::PitchBend.new(second_nibble, data_byte_1, data_byte_2)
37
+ end
38
+
39
+ def system_exclusive(*a)
40
+ ::MIDIMessage::SystemExclusive.new(*a)
41
+ end
42
+
43
+ def system_common(second_nibble, data_byte_1 = nil, data_byte_2 = nil)
44
+ ::MIDIMessage::SystemCommon.new(second_nibble, data_byte_1, data_byte_2)
45
+ end
46
+
47
+ def system_realtime(second_nibble)
48
+ ::MIDIMessage::SystemRealtime.new(second_nibble)
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,67 @@
1
+ require "midilib"
2
+
3
+ module Nibbler
4
+
5
+ # Construct messages with midilib in a generic way
6
+ # https://github.com/jimm/midilib
7
+ # midilib is copyright © 2003-2010 Jim Menard
8
+ module Midilib
9
+
10
+ extend self
11
+
12
+ def note_off(second_nibble, data_byte_1, data_byte_2)
13
+ MIDI::NoteOff.new(second_nibble, data_byte_1, data_byte_2)
14
+ end
15
+
16
+ def note_on(second_nibble, data_byte_1, data_byte_2)
17
+ MIDI::NoteOn.new(second_nibble, data_byte_1, data_byte_2)
18
+ end
19
+
20
+ def polyphonic_aftertouch(second_nibble, data_byte_1, data_byte_2)
21
+ MIDI::PolyPressure.new(second_nibble, data_byte_1, data_byte_2)
22
+ end
23
+
24
+ def control_change(second_nibble, data_byte_1, data_byte_2)
25
+ MIDI::Controller.new(second_nibble, data_byte_1, data_byte_2)
26
+ end
27
+
28
+ def program_change(second_nibble, data_byte)
29
+ MIDI::ProgramChange.new(second_nibble, data_byte)
30
+ end
31
+
32
+ def channel_aftertouch(second_nibble, data_byte)
33
+ MIDI::ChannelPressure.new(second_nibble, data_byte)
34
+ end
35
+
36
+ def pitch_bend(second_nibble, data_byte_1, data_byte_2)
37
+ # to-do handle the midilib lsb/msb
38
+ # right now the second data byte is being thrown away
39
+ MIDI:: PitchBend.new(second_nibble, data_byte_1, data_byte_2)
40
+ end
41
+
42
+ def system_exclusive(*a)
43
+ MIDI::SystemExclusive.new(a)
44
+ end
45
+
46
+ def system_common(second_nibble, data_byte_1 = nil, data_byte_2 = nil)
47
+ case second_nibble
48
+ when 0x2 then MIDI::SongPointer.new(data_byte_1) # similar issue to pitch bend here
49
+ when 0x3 then MIDI::SongSelect.new(data_byte_1)
50
+ when 0x6 then MIDI::TuneRequest.new
51
+ end
52
+ end
53
+
54
+ def system_realtime(second_nibble)
55
+ case second_nibble
56
+ when 0x8 then MIDI::Clock.new
57
+ when 0xA then MIDI::Start.new
58
+ when 0xB then MIDI::Continue.new
59
+ when 0xC then MIDI::Stop.new
60
+ when 0xE then MIDI::ActiveSense.new
61
+ when 0xF then MIDI::SystemReset.new
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -1,32 +1,23 @@
1
- #!/usr/bin/env ruby
2
- #
3
1
  module Nibbler
4
-
5
2
 
6
- # this is where messages go
7
3
  class Parser
8
-
4
+
9
5
  attr_reader :buffer
10
-
6
+
7
+ # @param [Hash] options
8
+ # @option options [Symbol] :message_lib
11
9
  def initialize(options = {})
12
10
  @running_status = nil
13
11
  @buffer = []
14
12
  @iterator = 0
15
-
16
- case options[:message_lib]
17
- when :midilib then
18
- require 'midilib'
19
- require 'nibbler/midilib_factory'
20
- @message_factory = MidilibFactory.new
21
- else
22
- require 'midi-message'
23
- require 'nibbler/midi-message_factory'
24
- @message_factory = MIDIMessageFactory.new
25
- end
13
+
14
+ initialize_message_library(options[:message_lib])
26
15
  end
27
16
 
17
+ # @param [Array<String, Fixnum>] nibbles
18
+ # @return [Hash]
28
19
  def process(nibbles)
29
- output = {
20
+ report = {
30
21
  :messages => [],
31
22
  :processed => [],
32
23
  :rejected => []
@@ -37,118 +28,119 @@ module Nibbler
37
28
  # iterate through nibbles until a status message is found
38
29
  # see if there really is a message there
39
30
  populate_current
40
- # current is the current piece of the buffer we're dealing with
41
- processed = nibbles_to_message
42
- unless processed[:message].nil?
43
- # if it's a real message, reject previous nibbles
44
- output[:rejected] += @buffer.slice(0, @iterator)
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)
45
35
  # and record it
46
36
  @buffer = @current # current now has the remaining nibbles for next pass
47
37
  @current = nil # reset current
48
38
  @iterator = 0 # reset iterator
49
- output[:messages] << processed[:message]
50
- output[:processed] += processed[:processed]
39
+ report[:messages] << processed[:message]
40
+ report[:processed] += processed[:processed]
51
41
  else
52
42
  @running_status = nil
53
43
  @iterator += 1
54
44
  end
55
45
  end
56
- output
46
+ report
57
47
  end
58
-
48
+
49
+ # @return [Hash, nil]
59
50
  def nibbles_to_message
60
- output = {
61
- :message => nil,
62
- :processed => [],
63
- :remaining => nil
64
- }
65
- return output if @current.length < 2
66
- first = @current[0].hex
67
- second = @current[1].hex
68
-
69
- output[:message], output[:processed] = *case first
70
- when 0x8 then lookahead(6) { |status_2, bytes| @message_factory.note_off(status_2, bytes[1], bytes[2]) }
71
- when 0x9 then lookahead(6) { |status_2, bytes| @message_factory.note_on(status_2, bytes[1], bytes[2]) }
72
- when 0xA then lookahead(6) { |status_2, bytes| @message_factory.polyphonic_aftertouch(status_2, bytes[1], bytes[2]) }
73
- when 0xB then lookahead(6) { |status_2, bytes| @message_factory.control_change(status_2, bytes[1], bytes[2]) }
74
- when 0xC then lookahead(4) { |status_2, bytes| @message_factory.program_change(status_2, bytes[1]) }
75
- when 0xD then lookahead(4) { |status_2, bytes| @message_factory.channel_aftertouch(status_2, bytes[1]) }
76
- when 0xE then lookahead(6) { |status_2, bytes| @message_factory.pitch_bend(status_2, bytes[1], bytes[2]) }
77
- when 0xF then case second
78
- when 0x0 then lookahead_sysex { |bytes| @message_factory.system_exclusive(*bytes) }
79
- when 0x1..0x6 then lookahead(6, :recursive => true) { |status_2, bytes| @message_factory.system_common(status_2, bytes[1], bytes[2]) }
80
- when 0x8..0xF then lookahead(2) { |status_2, bytes| @message_factory.system_realtime(status_2) }
81
- end
82
- else
83
- use_running_status if running_status_possible?
51
+ if @current.length >= 2
52
+ nibbles = @current.slice(0..1).map(&:hex)
53
+ compute_message(nibbles)
84
54
  end
85
- output
86
55
  end
87
-
56
+
88
57
  private
89
-
90
- def running_status_possible?
58
+
59
+ # @param [Array<Fixnum>] nibbles
60
+ # @return [Hash, nil]
61
+ def compute_message(nibbles)
62
+ 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
71
+ 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) }
75
+ end
76
+ else
77
+ use_running_status if possible_running_status?
78
+ end
79
+ end
80
+
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
91
+ end
92
+
93
+ def possible_running_status?
91
94
  !@running_status.nil?
92
95
  end
93
-
96
+
94
97
  def use_running_status
95
- lookahead(@running_status[:num], :status_nibble => @running_status[:status_nibble], &@running_status[:block])
98
+ lookahead(@running_status[:num], :status_nibble => @running_status[:status_nibble], &@running_status[:callback])
96
99
  end
97
-
100
+
98
101
  def populate_current
99
102
  @current = (@buffer[@iterator, (@buffer.length - @iterator)])
100
103
  end
101
-
102
- def lookahead(num, options = {}, &block)
103
- recursive = !options[:recursive].nil? && options[:recursive]
104
- status_nibble = options[:status_nibble]
105
- processed = []
106
- msg = nil
104
+
105
+ def lookahead(num, options = {}, &callback)
107
106
  # do we have enough nibbles for num bytes?
108
- if @current.slice(0, num).length >= num
107
+ if @current.size >= num
108
+ # 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]
109
111
 
110
- # if so shift those nibbles off of the array and call block with them
111
- processed += @current.slice!(0, num)
112
- status_nibble ||= processed[1]
113
112
  # send the nibbles to the block as bytes
114
113
  # return the evaluated block and the remaining nibbles
115
- bytes = TypeConversion.hex_chars_to_numeric_bytes(processed)
114
+ bytes = TypeConversion.hex_chars_to_numeric_bytes(nibbles)
115
+
116
116
  # record the current situation in case running status comes up next round
117
117
  @running_status = {
118
- :block => block,
118
+ :callback => callback,
119
119
  :num => num - 2,
120
120
  :status_nibble => status_nibble
121
121
  }
122
- msg = block.call(status_nibble.hex, bytes)
123
- elsif num > 0 && recursive
124
- msg, processed = *lookahead(num-2, options, &block)
122
+
123
+ {
124
+ :message => yield(status_nibble.hex, bytes),
125
+ :processed => nibbles
126
+ }
127
+ elsif num > 0 && !!options[:recursive]
128
+ lookahead(num - 2, options, &callback)
125
129
  end
126
- [msg, processed]
127
130
  end
128
-
131
+
129
132
  def lookahead_sysex(&block)
130
- processed = []
131
- msg = nil
132
133
  @running_status = nil
133
-
134
+
134
135
  bytes = TypeConversion.hex_chars_to_numeric_bytes(@current)
135
- ind = bytes.index(0xF7)
136
- unless ind.nil?
137
- msg = block.call(bytes.slice!(0, ind + 1))
138
- processed += @current.slice!(0, (ind + 1) * 2)
136
+ unless (index = bytes.index(0xF7)).nil?
137
+ {
138
+ :message => yield(bytes.slice!(0, index + 1)),
139
+ :processed => @current.slice!(0, (index + 1) * 2)
140
+ }
139
141
  end
140
- [msg, processed]
141
142
  end
142
-
143
- # for testing
144
- def buffer=(val)
145
- @buffer=val
146
- end
147
-
148
- def current
149
- @current
150
- end
151
-
143
+
152
144
  end
153
145
 
154
- end
146
+ end