micromidi 0.0.7

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 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,103 @@
1
+ = micromidi
2
+
3
+ A Ruby DSL for MIDI
4
+
5
+ {pic}[http://images.treetrouble.net/images/midi.png]
6
+
7
+ == Features
8
+
9
+ * Cross-platform compatible using MRI or JRuby.
10
+ * Simplified MIDI and Sysex message output
11
+ * MIDI Thru, processing and custom input events
12
+ * Optional shorthand for {live coding}[http://en.wikipedia.org/wiki/Live_coding]
13
+
14
+ == Installation
15
+
16
+ gem install micromidi
17
+
18
+ == Requirements
19
+
20
+ Ruby 1.9.2+ or JRuby in 1.9 mode
21
+
22
+ Requires {midi-eye}[http://github.com/arirusso/midi-eye], {midi-message}[http://github.com/arirusso/midi-message] and {unimidi}[http://github.com/arirusso/unimidi]. These should install automatically with the gem.
23
+
24
+ == Usage
25
+
26
+ The following are basic examples that use {unimidi}[http://github.com/arirusso/unimidi] inputs and outputs. ({see an example here that explains selecting an output...}[http://github.com/arirusso/unimidi/blob/master/examples/select_a_device.rb])
27
+
28
+ require "midi"
29
+
30
+ @i = UniMIDI::Input.use(:first)
31
+ @o = UniMIDI::Output.use(:first)
32
+
33
+ This example plays some arpeggios
34
+
35
+ MIDI.using($o) do
36
+
37
+ 5.times do |oct|
38
+ octave oct
39
+ %w{C E G B}.each { |n| play n 0.5 }
40
+ end
41
+
42
+ end
43
+
44
+ While running, this next example sends all input directly to the output except for notes; notes that are received are only sent to the output if their octave is between 1 and 3. Output is also printed to the console by passing in <em>$stdout</em>.
45
+
46
+ MIDI.using(@i, @o, $stdout) do
47
+
48
+ thru_except :note { |msg| only(msg, :octave, (1..3)) }
49
+
50
+ join
51
+
52
+ end
53
+
54
+ This is the same example redone using shorthand aliases
55
+
56
+ M(@i, @o) do
57
+
58
+ te :n { |m| only(m, :oct, (1..3)) }
59
+
60
+ j
61
+
62
+ end
63
+
64
+ Finally, here is an example that maps some MIDI Control Change messages to SysEx
65
+
66
+ MIDI.using(@i, @o) do
67
+
68
+ *@the_map =
69
+ [0x40, 0x7F, 0x00],
70
+ [0x41, 0x7F, 0x00],
71
+ [0x42, 0x7F, 0x00]
72
+
73
+ node :roland, :model_id => 0x42, :device_id => 0x10
74
+
75
+ receive :cc do |message|
76
+
77
+ command @the_map[message.index - 1], message.value
78
+
79
+ end
80
+
81
+ end
82
+
83
+ I've written up a few posts explaining each of the concepts used here in greater detail:
84
+
85
+ * {Output}[http://tx81z.blogspot.com/2011/08/micromidi-midi-messages-and-output.html]
86
+ * {MIDI Thru and Processing}[http://tx81z.blogspot.com/2011/08/micromidi-midi-thru-and-midi-processing.html]
87
+ * {Binding Custom Input Events}[http://tx81z.blogspot.com/2011/08/micromidi-custom-events.html]
88
+ * {Shorthand}[http://tx81z.blogspot.com/2011/08/micromidi-shorthand.html]
89
+ * {Sysex}[http://tx81z.blogspot.com/2011/09/generating-sysex-messages-with.html]
90
+
91
+ == Documentation
92
+
93
+ * {rdoc}[http://rubydoc.info/github/arirusso/micromidi]
94
+
95
+ == Author
96
+
97
+ * {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
98
+
99
+ == License
100
+
101
+ Apache 2.0, See the file LICENSE
102
+
103
+ Copyright (c) 2011 Ari Russo
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ Functionality
2
+ * thru issues / strong thru
3
+ * Sysex messages
4
+ * MIDI files?
5
+
6
+ Documentation
7
+ * Guide for shorthand aliases
8
+ * More rdoc
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ module MicroMIDI
5
+
6
+ class Context
7
+
8
+ include Instructions::Composite
9
+
10
+ attr_reader :state
11
+
12
+ def initialize(ins, outs, &block)
13
+
14
+ @state = State.new(ins, outs)
15
+
16
+ @instructions = {
17
+ :process => Instructions::Process.new(@state),
18
+ :input => Instructions::Input.new(@state),
19
+ :message => Instructions::Message.new(@state),
20
+ :output => Instructions::Output.new(@state),
21
+ :sticky => Instructions::Sticky.new(@state),
22
+ :sysex => Instructions::SysEx.new(@state)
23
+ }
24
+
25
+ instance_eval(&block) unless block.nil?
26
+ end
27
+
28
+ def repeat
29
+ self.send(@state.last_command[:method], *@state.last_command[:args]) unless @state.last_command.nil?
30
+ end
31
+
32
+ def method_missing(m, *a, &b)
33
+ delegated = false
34
+ outp = nil
35
+ options = a.last.kind_of?(Hash) ? a.last : {}
36
+ do_output = options[:output] || true
37
+ [@instructions[:sysex], @instructions[:message], @instructions[:process]].each do |dsl|
38
+ if dsl.respond_to?(m)
39
+ msg = dsl.send(m, *a, &b)
40
+ outp = @state.auto_output && do_output ? @instructions[:output].output(msg) : msg
41
+ delegated = true
42
+ end
43
+ end
44
+ unless delegated
45
+ [@instructions[:input], @instructions[:output], @instructions[:sticky]].each do |dsl|
46
+ if dsl.respond_to?(m)
47
+ outp = dsl.send(m, *a, &b)
48
+ delegated = true
49
+ end
50
+ end
51
+ end
52
+ @state.record(m, a, b, outp)
53
+ delegated ? outp : super
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ module Composite
8
+
9
+ # play note n, wait <em>duration</em> seconds, then do note off for note n
10
+ def play(n, duration)
11
+ msg = case n
12
+ when Numeric, String then note(n)
13
+ when MIDIMessage then n
14
+ end
15
+ sleep(duration)
16
+ off
17
+ msg
18
+ end
19
+
20
+ # sends a note off message for every note on every channel
21
+ def all_off
22
+ (0..15).each do |channel|
23
+ (0..127).each do |note_num|
24
+ note_off(note_num, :channel => channel)
25
+ end
26
+ end
27
+ true
28
+ end
29
+ alias_method :quiet!, :all_off
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class Input
8
+
9
+ include MIDIMessage
10
+
11
+ def initialize(state)
12
+ @state = state
13
+ end
14
+
15
+ # bind an event that will be called every time a message is received
16
+ def receive(*a, &block)
17
+ options = a.last.kind_of?(Hash) ? a.pop : {}
18
+ match = a.empty? ? nil : { :class => msg_classes(a) }
19
+ listener(match, options) { |event| yield(event[:message], event[:timestamp]) }
20
+ end
21
+ alias_method :gets, :receive
22
+ alias_method :handle, :receive
23
+ alias_method :listen, :receive
24
+ alias_method :listen_for, :receive
25
+ alias_method :when_receive, :receive
26
+
27
+ def receive_unless(*a, &block)
28
+ options = a.last.kind_of?(Hash) ? a.pop : {}
29
+ match = { :class => msg_classes(a) }
30
+ listener(nil, options) { |event| yield(event[:message], event[:timestamp]) unless match.include?(event[:message].class) }
31
+ end
32
+ alias_method :handle_unless, :receive_unless
33
+ alias_method :listen_unless, :receive_unless
34
+ alias_method :listen_for_unless, :receive_unless
35
+ alias_method :unless_receive, :receive_unless
36
+
37
+ # send input messages thru to the outputs
38
+ def thru
39
+ thru_if
40
+ end
41
+
42
+ # send input messages thru to the outputs if it has a specific class
43
+ def thru_if(*a)
44
+ a.last.kind_of?(Hash) ? a.last[:thru] = true : a.push({ :thru => true })
45
+ receive(*a) { |message, timestamp| output(message) }
46
+ end
47
+
48
+ # send input messages thru to the outputs unless of a specific class
49
+ def thru_unless(*a)
50
+ a.last.kind_of?(Hash) ? a.last[:thru] = true : a.push({ :thru => true })
51
+ receive_unless(*a) { |message, timestamp| output(message) }
52
+ end
53
+
54
+ # like <em>thru_unless</em> except a block can be passed that will be called when
55
+ # notes specified as the <em>unless</em> arrive
56
+ def thru_except(*a, &block)
57
+ thru_unless(*a)
58
+ receive(*a, &block)
59
+ end
60
+
61
+ # wait for input on the last input passed in
62
+ # can pass the option :from => [an input] to specify which one to wait on
63
+ def wait_for_input(options = {})
64
+ listener = options[:from] || @state.listeners.last || @state.thru_listeners.last
65
+ listener.join
66
+ end
67
+
68
+ def join
69
+ loop { wait_for_input }
70
+ end
71
+
72
+ protected
73
+
74
+ def listener(match = {}, options = {}, &block)
75
+ inputs = options[:from] || @state.inputs
76
+ thru = options[:thru] || false
77
+ match ||= {}
78
+ inputs.each do |input|
79
+ listener = MIDIEye::Listener.new(input)
80
+ listener.listen_for(match, &block)
81
+ if thru
82
+ @state.thru_listeners.each { |l| l.stop }
83
+ @state.thru_listeners.clear
84
+ @state.thru_listeners << listener
85
+ else
86
+ @state.listeners << listener
87
+ end
88
+ listener.start(:background => true) unless !options[:start].nil? && !options[:start]
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def msg_classes(list)
95
+ list.map do |type|
96
+ case type
97
+ when :aftertouch, :pressure, :aft then [ChannelAftertouch, PolyphonicAftertouch]
98
+ when :channel_aftertouch, :channel_pressure, :ca, :cp then ChannelAftertouch
99
+ when :control_change, :cc, :c then ControlChange
100
+ when :note, :n then [NoteOn, NoteOff]
101
+ when :note_on, :nn then NoteOn
102
+ when :note_off, :no, :off then NoteOff
103
+ when :pitch_bend, :pb then PitchBend
104
+ when :polyphonic_aftertouch, :poly_aftertouch, :poly_pressure, :polyphonic_pressure, :pa, :pp then PolyphonicAftertouch
105
+ when :program_change, :pc, :p then ProgramChange
106
+ end
107
+ end.flatten.compact
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class Message
8
+
9
+ include MIDIMessage
10
+
11
+ def initialize(state)
12
+ @state = state
13
+ end
14
+
15
+ # create a control change message
16
+ def control_change(id, value, opts = {})
17
+ props = @state.message_properties(opts, :channel)
18
+ id.kind_of?(Numeric) ? ControlChange.new(props[:channel], id, value) : ControlChange[id].new(props[:channel], value)
19
+ end
20
+
21
+ # create a note message
22
+ def note(id, opts = {})
23
+ props = @state.message_properties(opts, :channel, :velocity)
24
+ note = if id.kind_of?(Numeric)
25
+ NoteOn.new(props[:channel], id, props[:velocity])
26
+ elsif id.kind_of?(String) || id.kind_of?(Symbol)
27
+ note_string = parse_note_name(id)
28
+ NoteOn[note_string].new(props[:channel], props[:velocity])
29
+ end
30
+ @state.last_note = note
31
+ note
32
+ end
33
+
34
+ # create a note off message
35
+ def note_off(id, opts = {})
36
+ props = @state.message_properties(opts, :channel, :velocity)
37
+ if id.kind_of?(Numeric)
38
+ NoteOff.new(props[:channel], id, props[:velocity])
39
+ elsif id.kind_of?(String) || id.kind_of?(Symbol)
40
+ note_string = parse_note_name(id)
41
+ NoteOff[parse_note_name(id)].new(props[:channel], props[:velocity])
42
+ end
43
+ end
44
+
45
+ # create a MIDI message from a byte string, array of bytes, or list of bytes
46
+ def parse(message)
47
+ MIDIMessage.parse(message)
48
+ end
49
+
50
+ # create a program change message
51
+ def program_change(program, opts = {})
52
+ props = @state.message_properties(opts, :channel)
53
+ MIDIMessage::ProgramChange.new(props[:channel], program)
54
+ end
55
+
56
+ # create a note-off message from the last note-on message
57
+ def off
58
+ o = @state.last_note.to_note_off unless @state.last_note.nil?
59
+ @state.last_note = nil
60
+ o
61
+ end
62
+
63
+ # create a channel pressure message
64
+ def channel_aftertouch(value, opts = {})
65
+ props = @state.message_properties(opts, :channel)
66
+ MIDIMessage::ChannelAftertouch.new(props[:channel], value)
67
+ end
68
+ alias_method :channel_pressure, :channel_aftertouch
69
+
70
+ # create a poly pressure message
71
+ def polyphonic_aftertouch(note, value, opts = {})
72
+ props = @state.message_properties(opts, :channel)
73
+ MIDIMessage::PolyphonicAftertouch.new(props[:channel], note, value)
74
+ end
75
+ alias_method :poly_aftertouch, :polyphonic_aftertouch
76
+ alias_method :polyphonic_pressure, :polyphonic_aftertouch
77
+ alias_method :poly_pressure, :polyphonic_aftertouch
78
+
79
+ def pitch_bend(low, high, opts = {})
80
+ props = @state.message_properties(opts, :channel)
81
+ MIDIMessage::PitchBend.new(props[:channel], low, high)
82
+ end
83
+ alias_method :bend, :pitch_bend
84
+ alias_method :pitchbend, :pitch_bend
85
+
86
+ protected
87
+
88
+ def parse_note_name(name)
89
+ name = name.to_s #ensure that name is a string
90
+ string_opts = { :octave => name.scan(/-?\d\z/).first }
91
+ note = name.split(/-?\d\z/).first
92
+ string_props = @state.message_properties(string_opts, :octave)
93
+ "#{note}#{string_props[:octave].to_s}"
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class Output
8
+
9
+ def initialize(state)
10
+ @state = state
11
+ end
12
+
13
+ def output(msg)
14
+ auto_output(msg) if msg === false || msg === true
15
+ @state.outputs.each { |o| o.puts(msg) } unless msg.nil?
16
+ msg
17
+ end
18
+
19
+ # toggle mode where messages are automatically outputted
20
+ def auto_output(mode = nil)
21
+ mode.nil? ? @state.toggle_auto_output : @state.auto_output = mode
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class Process
8
+
9
+ include MIDIMessage
10
+ include MIDIMessage::Process
11
+
12
+ def initialize(state)
13
+ @state = state
14
+ end
15
+
16
+ def transpose(message, property, factor, options = {})
17
+ Transpose.process(message, property, factor, options)
18
+ end
19
+
20
+ def limit(message, property, range, options = {})
21
+ Limit.process(message, property, range, options)
22
+ end
23
+
24
+ def filter(message, property, bandwidth, options = {})
25
+ Filter.process(message, property, bandwidth, options)
26
+ end
27
+
28
+ def high_pass_filter(message, property, min, options = {})
29
+ HighPassFilter.process(message, property, min, options)
30
+ end
31
+ alias_method :only_above, :high_pass_filter
32
+ alias_method :except_below, :high_pass_filter
33
+
34
+ def low_pass_filter(message, property, max, options = {})
35
+ LowPassFilter.process(message, property, max, options)
36
+ end
37
+ alias_method :only_below, :low_pass_filter
38
+ alias_method :except_above, :low_pass_filter
39
+
40
+ def band_pass_filter(message, property, bandwidth, options = {})
41
+ BandPassFilter.process(message, property, bandwidth, options)
42
+ end
43
+ alias_method :only_in, :band_pass_filter
44
+ alias_method :only, :band_pass_filter
45
+
46
+ def notch_filter(message, property, bandwidth, options = {})
47
+ NotchFilter.process(message, property, bandwidth, options)
48
+ end
49
+ alias_method :band_reject_filter, :notch_filter
50
+ alias_method :except_in, :notch_filter
51
+ alias_method :except, :notch_filter
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ module MicroMIDI
5
+
6
+ alias l loop
7
+
8
+ class << self
9
+ alias_method :m, :message
10
+ end
11
+
12
+ class Context
13
+ alias_method :r, :repeat
14
+ end
15
+
16
+ module Instructions
17
+
18
+ module Composite
19
+ alias_method :p, :play
20
+ alias_method :q!, :all_off
21
+ alias_method :x, :all_off
22
+ end
23
+
24
+ class Input
25
+ alias_method :j, :join
26
+ alias_method :rc, :receive
27
+ alias_method :rcu, :receive_unless
28
+ alias_method :t, :thru
29
+ alias_method :te, :thru_except
30
+ alias_method :tu, :thru_unless
31
+ alias_method :w, :wait_for_input
32
+ end
33
+
34
+ class Message
35
+ alias_method :c, :control_change
36
+ alias_method :cc, :control_change
37
+ alias_method :n, :note
38
+ alias_method :no, :note_off
39
+ alias_method :o, :off
40
+ alias_method :pc, :program_change
41
+ end
42
+
43
+ class Output
44
+ alias_method :out, :output
45
+ end
46
+
47
+ class Process
48
+ alias_method :bp, :band_pass_filter
49
+ alias_method :bpf, :band_pass_filter
50
+ alias_method :br, :notch_filter
51
+ alias_method :f, :filter
52
+ alias_method :hp, :high_pass_filter
53
+ alias_method :hpf, :high_pass_filter
54
+ alias_method :l, :limit
55
+ alias_method :lp, :low_pass_filter
56
+ alias_method :lpf, :low_pass_filter
57
+ alias_method :mbf, :filter
58
+ alias_method :nf, :notch_filter
59
+ alias_method :tp, :transpose
60
+ end
61
+
62
+ class Sticky
63
+ alias_method :ch, :channel
64
+ alias_method :ss, :super_sticky
65
+ alias_method :v, :velocity
66
+ end
67
+
68
+ class SysEx
69
+ alias_method :sc, :sysex_command
70
+ alias_method :sr, :sysex_request
71
+ alias_method :sx, :sysex_message
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+ def M(*a, &block)
78
+ MIDI.message(*a, &block)
79
+ end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class Sticky
8
+
9
+ def initialize(state)
10
+ @state = state
11
+ end
12
+
13
+ # sets the sticky channel for the current block
14
+ def channel(val = nil)
15
+ val.nil? ? @state.channel : @state.channel = val
16
+ end
17
+
18
+ # sets the octave for the current block
19
+ def octave(val = nil)
20
+ val.nil? ? @state.octave : @state.octave = val
21
+ end
22
+
23
+ # sets the sysex node for the current block
24
+ def sysex_node(*args)
25
+ options = args.last.kind_of?(Hash) ? args.last : {}
26
+ args.empty? ? @state.sysex_node : @state.sysex_node = MIDIMessage::SystemExclusive::Node.new(args.first, options)
27
+ end
28
+ alias_method :node, :sysex_node
29
+
30
+ # sets the sticky velocity for the current block
31
+ def velocity(val = nil)
32
+ val.nil? ? @state.velocity : @state.velocity = val
33
+ end
34
+
35
+ #
36
+ # toggles super_sticky mode, a mode where any explicit values used to create MIDI messages
37
+ # automatically become sticky -- whereas normally the explicit value would only be used for
38
+ # the current message.
39
+ #
40
+ # e.g.
41
+ #
42
+ # note "C4", :channel => 5
43
+ #
44
+ # will have the exact same effect as
45
+ #
46
+ # channel 5
47
+ # note "C4"
48
+ #
49
+ # while in super sticky mode
50
+ #
51
+ def super_sticky
52
+ @state.toggle_super_sticky
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ module MicroMIDI
4
+
5
+ module Instructions
6
+
7
+ class SysEx
8
+
9
+ include MIDIMessage
10
+
11
+ def initialize(state)
12
+ @state = state
13
+ end
14
+
15
+ # create a sysex command
16
+ def sysex_command(address, data, options = {})
17
+ options[:sysex_node] ||= options[:node]
18
+ props = @state.message_properties(options, :sysex_node)
19
+ SystemExclusive::Command.new(address, data, :node => props[:sysex_node])
20
+ end
21
+ alias_method :command, :sysex_command
22
+
23
+ # create a sysex request
24
+ def sysex_request(address, size, options = {})
25
+ options[:sysex_node] ||= options[:node]
26
+ props = @state.message_properties(options, :sysex_node)
27
+ SystemExclusive::Request.new(address, size, :node => props[:sysex_node])
28
+ end
29
+ alias_method :request, :sysex_request
30
+
31
+ # create an indeterminate sysex message
32
+ def sysex_message(data, options = {})
33
+ options[:sysex_node] ||= options[:node]
34
+ props = @state.message_properties(options, :sysex_node)
35
+ SystemExclusive::Message.new(data, :node => props[:sysex_node])
36
+ end
37
+ alias_method :sysex, :sysex_message
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end