midi-nibbler 0.0.3

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