midi-nibbler 0.0.3

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.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2010-2011 Ari Russo
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,106 @@
1
+ = nibbler
2
+
3
+ {cat on arp}[http://images.treetrouble.net/images/dog_with_synth.jpg]
4
+
5
+ Parse MIDI Messages
6
+
7
+ == Requirements
8
+
9
+ * {midi-message}[http://github.com/arirusso/midi-message]
10
+
11
+ == Install
12
+
13
+ gem install midi-nibbler
14
+
15
+ == Usage
16
+
17
+ require 'nibbler'
18
+
19
+ nibbler = Nibbler.new
20
+
21
+ Enter a message piece by piece
22
+
23
+ p nibbler.parse("90")
24
+ nil
25
+
26
+ p nibbler.parse("40")
27
+ nil
28
+
29
+ p nibbler.parse("40")
30
+ # #<MIDIMessage::NoteOn:0x98c9818
31
+ # @channel=0,
32
+ # @data=[64, 100],
33
+ # @name="C3",
34
+ # @note=64,
35
+ # @status=[9, 0],
36
+ # @velocity=100,
37
+ # @verbose_name="Note On: C3">
38
+
39
+ Enter a message all at once
40
+
41
+ p nibbler.parse("904040")
42
+
43
+ # #<MIDIMessage::NoteOn:0x98c9818
44
+ # @channel=0,
45
+ # @data=[64, 100],
46
+ # @name="C3",
47
+ # @note=64,
48
+ # @status=[9, 0],
49
+ # @velocity=100,
50
+ # @verbose_name="Note On: C3">
51
+
52
+ Use bytes
53
+
54
+ p nibbler.parse(0x90, 0x40, 0x40)
55
+ #<MIDIMessage::NoteOn:0x98c9818 ...>
56
+
57
+ You can use nibbles in string format
58
+
59
+ p nibbler.parse("9", "0", "4", "0", "4", "0")
60
+ #<MIDIMessage::NoteOn:0x98c9818 ...>
61
+
62
+ Interchange the different types
63
+
64
+ p nibbler.parse("9", "0", 0x40, 64)
65
+ #<MIDIMessage::NoteOn:0x98c9818 ...>
66
+
67
+ Use running status
68
+
69
+ p nibbler.parse(0x40, 64)
70
+ #<MIDIMessage::NoteOn:0x98c9818 ...>
71
+
72
+ Look at the messages we've parsed
73
+
74
+ p nibbler.messages
75
+ [#<MIDIMessage::NoteOn:0x98c9804 ...>
76
+ #<MIDIMessage::NoteOn:0x98c9811 ...>]
77
+
78
+ Add an incomplete message
79
+
80
+ p nibbler.parse("9")
81
+ p nibbler.parse("40")
82
+
83
+ See progress
84
+
85
+ p nibbler.buffer
86
+ ["9", "4", "0"]
87
+
88
+ p nibbler.buffer_hex
89
+ "940"
90
+
91
+ 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]
92
+
93
+ Nibbler.new(:message_lib => :midilib)
94
+
95
+ p nibbler.parse("9", "0", 0x40, "40")
96
+ "0: ch 00 on 40 40"
97
+
98
+ == Author
99
+
100
+ * {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
101
+
102
+ == License
103
+
104
+ Apache 2.0, See the file LICENSE
105
+
106
+ Copyright (c) 2011 Ari Russo
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+ # Turns various types of input in to an array of hex digit chars
6
+ class HexCharArrayFilter
7
+
8
+ # returns an array of hex string nibbles
9
+ def process(*a)
10
+ a.flatten!
11
+ buf = []
12
+ a.each do |thing|
13
+ buf += case thing
14
+ when Array then thing.map { |arr| to_nibbles(*arr) }.inject { |a,b| a + b }
15
+ when String then TypeConversion.hex_str_to_hex_chars(filter_string(thing))
16
+ when Numeric then TypeConversion.numeric_byte_to_hex_chars(filter_numeric(thing))
17
+ end
18
+ end
19
+ buf.compact.map { |n| n.upcase }
20
+ end
21
+
22
+ private
23
+
24
+ # limit <em>num</em> to bytes usable in MIDI ie values (0..240)
25
+ # returns nil if the byte is outside of that range
26
+ def filter_numeric(num)
27
+ (0x00..0xFF).include?(num) ? num : nil
28
+ end
29
+
30
+ # get rid of non-hex string characters
31
+ def filter_string(str)
32
+ str.gsub(/[^0-9a-fA-F]/, '').upcase
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+ # factory for constructing messages with {midi-message}(http://github.com/arirusso/midi-message)
6
+ class MIDIMessageFactory
7
+
8
+ include MIDIMessage
9
+
10
+ def note_off(second_nibble, data_byte_1, data_byte_2)
11
+ NoteOff.new(second_nibble, data_byte_1, data_byte_2)
12
+ end
13
+
14
+ def note_on(second_nibble, data_byte_1, data_byte_2)
15
+ NoteOn.new(second_nibble, data_byte_1, data_byte_2)
16
+ end
17
+
18
+ def polyphonic_aftertouch(second_nibble, data_byte_1, data_byte_2)
19
+ PolyphonicAftertouch.new(second_nibble, data_byte_1, data_byte_2)
20
+ end
21
+
22
+ def control_change(second_nibble, data_byte_1, data_byte_2)
23
+ ControlChange.new(second_nibble, data_byte_1, data_byte_2)
24
+ end
25
+
26
+ def program_change(second_nibble, data_byte)
27
+ ProgramChange.new(second_nibble, data_byte)
28
+ end
29
+
30
+ def channel_aftertouch(second_nibble, data_byte)
31
+ ChannelAftertouch.new(second_nibble, data_byte)
32
+ end
33
+
34
+ def pitch_bend(second_nibble, data_byte_1, data_byte_2)
35
+ PitchBend.new(second_nibble, data_byte_1, data_byte_2)
36
+ end
37
+
38
+ def system_exclusive(*a)
39
+ SystemExclusive.new(*a)
40
+ end
41
+
42
+ def system_common(second_nibble, data_byte_1 = nil, data_byte_2 = nil)
43
+ SystemCommon.new(second_nibble, data_byte_1, data_byte_2)
44
+ end
45
+
46
+ def system_realtime(second_nibble)
47
+ SystemRealtime.new(second_nibble)
48
+ end
49
+
50
+
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+ # factory for constructing messages with {midilib}(https://github.com/jimm/midilib)
6
+ # midilib is copyright © 2003-2010 Jim Menard
7
+ class MidilibFactory
8
+
9
+ include MIDI
10
+
11
+ def note_off(second_nibble, data_byte_1, data_byte_2)
12
+ 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
+ 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
+ PolyPressure.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
+ Controller.new(second_nibble, data_byte_1, data_byte_2)
25
+ end
26
+
27
+ def program_change(second_nibble, data_byte)
28
+ ProgramChange.new(second_nibble, data_byte)
29
+ end
30
+
31
+ def channel_aftertouch(second_nibble, data_byte)
32
+ ChannelPressure.new(second_nibble, data_byte)
33
+ end
34
+
35
+ def pitch_bend(second_nibble, data_byte_1, data_byte_2)
36
+ # to-do handle the midilib lsb/msb
37
+ # right now the second data byte is being thrown away
38
+ PitchBend.new(second_nibble, data_byte_1, data_byte_2)
39
+ end
40
+
41
+ def system_exclusive(*a)
42
+ SystemExclusive.new(a)
43
+ end
44
+
45
+ def system_common(second_nibble, data_byte_1 = nil, data_byte_2 = nil)
46
+ case second_nibble
47
+ when 0x2 then SongPointer.new(data_byte_1) # similar issue to pitch bend here
48
+ when 0x3 then SongSelect.new(data_byte_1)
49
+ when 0x6 then TuneRequest.new
50
+ end
51
+ end
52
+
53
+ def system_realtime(second_nibble)
54
+ case second_nibble
55
+ when 0x8 then Clock.new
56
+ when 0xA then Start.new
57
+ when 0xB then Continue.new
58
+ when 0xC then Stop.new
59
+ when 0xE then ActiveSense.new
60
+ when 0xF then SystemReset.new
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+ # this is the entry point to the app
6
+ class Nibbler
7
+
8
+ extend Forwardable
9
+
10
+ attr_reader :messages,
11
+ :processed,
12
+ :rejected
13
+
14
+ # this class holds on to all output except for the buffer because the data in the buffer
15
+ # is the only data that's relevant between calls of Parser.process
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
+ def initialize(options = {}, &block)
23
+ @processed, @rejected, @messages = [], [], []
24
+ @parser = Parser.new(options)
25
+ @typefilter = HexCharArrayFilter.new
26
+ block.call unless block.nil?
27
+ end
28
+
29
+ def all_messages
30
+ @messages | @fragmented_messages
31
+ end
32
+
33
+ def buffer_hex
34
+ buffer.join
35
+ end
36
+
37
+ def clear_buffer
38
+ buffer.clear
39
+ end
40
+
41
+ def clear_messages
42
+ @messages.clear
43
+ end
44
+
45
+ def parse(*a)
46
+ queue = @typefilter.process(a)
47
+ result = @parser.process(queue)
48
+ @messages += result[:messages]
49
+ @processed += result[:processed]
50
+ @rejected += result[:rejected]
51
+ #@buffer = result[:remaining]
52
+ # return type
53
+ # 0 messages: nil
54
+ # 1 message: the message
55
+ # >1 message: an array of messages
56
+ # might make sense to make this an array no matter what...
57
+ if result[:messages].length < 2
58
+ (result[:messages].empty? ? nil : result[:messages][0])
59
+ else
60
+ result[:messages]
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+
6
+ # this is where messages go
7
+ class Parser
8
+
9
+ attr_reader :buffer
10
+
11
+ def initialize(options = {})
12
+ @running_status = nil
13
+ @buffer = []
14
+ @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
26
+ end
27
+
28
+ def process(nibbles)
29
+ output = {
30
+ :messages => [],
31
+ :processed => [],
32
+ :rejected => []
33
+ }
34
+ @iterator = 0
35
+ @buffer += nibbles
36
+ while @iterator <= (@buffer.length - 1)
37
+ # iterate through nibbles until a status message is found
38
+ # see if there really is a message there
39
+ 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)
45
+ # and record it
46
+ @buffer = @current # current now has the remaining nibbles for next pass
47
+ @current = nil # reset current
48
+ @iterator = 0 # reset iterator
49
+ output[:messages] << processed[:message]
50
+ output[:processed] += processed[:processed]
51
+ else
52
+ @running_status = nil
53
+ @iterator += 1
54
+ end
55
+ end
56
+ output
57
+ end
58
+
59
+ 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?
84
+ end
85
+ output
86
+ end
87
+
88
+ private
89
+
90
+ def running_status_possible?
91
+ !@running_status.nil?
92
+ end
93
+
94
+ def use_running_status
95
+ lookahead(@running_status[:num], :status_nibble => @running_status[:status_nibble], &@running_status[:block])
96
+ end
97
+
98
+ def populate_current
99
+ @current = (@buffer[@iterator, (@buffer.length - @iterator)])
100
+ 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
107
+ # do we have enough nibbles for num bytes?
108
+ if @current.slice(0, num).length >= num
109
+
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
+ # send the nibbles to the block as bytes
114
+ # return the evaluated block and the remaining nibbles
115
+ bytes = TypeConversion.hex_chars_to_numeric_bytes(processed)
116
+ # record the current situation in case running status comes up next round
117
+ @running_status = {
118
+ :block => block,
119
+ :num => num - 2,
120
+ :status_nibble => status_nibble
121
+ }
122
+ msg = block.call(status_nibble.hex, bytes)
123
+ elsif num > 0 && recursive
124
+ msg, processed = *lookahead(num-2, options, &block)
125
+ end
126
+ [msg, processed]
127
+ end
128
+
129
+ def lookahead_sysex(&block)
130
+ processed = []
131
+ msg = nil
132
+ @running_status = nil
133
+
134
+ 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)
139
+ end
140
+ [msg, processed]
141
+ end
142
+
143
+ # for testing
144
+ def buffer=(val)
145
+ @buffer=val
146
+ end
147
+
148
+ def current
149
+ @current
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module Nibbler
4
+
5
+ # this is a helper for converting nibbles and bytes
6
+ module TypeConversion
7
+
8
+ def self.hex_chars_to_numeric_bytes(nibbles)
9
+ nibbles = nibbles.dup
10
+ # get rid of last nibble if there's an odd number
11
+ # it will be processed later anyway
12
+ nibbles.slice!(nibbles.length-2, 1) if nibbles.length.odd?
13
+ bytes = []
14
+ while !(nibs = nibbles.slice!(0,2)).empty?
15
+ byte = (nibs[0].hex << 4) + nibs[1].hex
16
+ bytes << byte
17
+ end
18
+ bytes
19
+ end
20
+
21
+ # converts a string of hex digits to bytes
22
+ def self.hex_str_to_hex_chars(str)
23
+ str.split(//)
24
+ end
25
+
26
+ def self.numeric_byte_to_hex_chars(num)
27
+ [((num & 0xF0) >> 4), (num & 0x0F)].map { |n| n.to_s(16) }
28
+ end
29
+
30
+
31
+ end
32
+
33
+ end
data/lib/nibbler.rb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Parse MIDI Messages
4
+ # (c)2011 Ari Russo and licensed under the Apache 2.0 License
5
+ #
6
+
7
+ require 'forwardable'
8
+
9
+ require 'nibbler/nibbler'
10
+ require 'nibbler/parser'
11
+ require 'nibbler/type_conversion'
12
+ require 'nibbler/hex_char_array_filter'
13
+
14
+ #
15
+ # Parse MIDI Messages
16
+ #
17
+ module Nibbler
18
+
19
+ VERSION = "0.0.3"
20
+
21
+ # shortcut to Parser.new
22
+ def self.new(*a, &block)
23
+ Nibbler.new(*a, &block)
24
+ end
25
+
26
+ end
27
+
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: midi-nibbler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.3
6
+ platform: ruby
7
+ authors:
8
+ - Ari Russo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-04 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: midi-message
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: midilib
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: Parse MIDI Messages.
39
+ email:
40
+ - ari.russo@gmail.com
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ files:
48
+ - lib/nibbler.rb
49
+ - lib/nibbler/nibbler.rb
50
+ - lib/nibbler/midi-message_factory.rb
51
+ - lib/nibbler/parser.rb
52
+ - lib/nibbler/midilib_factory.rb
53
+ - lib/nibbler/hex_char_array_filter.rb
54
+ - lib/nibbler/type_conversion.rb
55
+ - LICENSE
56
+ - README.rdoc
57
+ has_rdoc: true
58
+ homepage: http://github.com/arirusso/nibbler
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.6
78
+ requirements: []
79
+
80
+ rubyforge_project: nibbler
81
+ rubygems_version: 1.6.2
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Parse MIDI Messages.
85
+ test_files: []
86
+