micromidi 0.0.7

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 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