midi 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.
@@ -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,67 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # micromidi
4
+ # A Ruby DSL for MIDI
5
+ #
6
+ # (c)2011 Ari Russo
7
+ # licensed under the Apache 2.0 License
8
+ #
9
+
10
+ # libs
11
+ require 'midi-eye'
12
+ require 'midi-message'
13
+ require 'unimidi'
14
+
15
+ module MicroMIDI
16
+
17
+ VERSION = "0.0.7"
18
+
19
+ module Instructions
20
+ end
21
+
22
+ def self.new(*a, &block)
23
+ message(*a, &block)
24
+ end
25
+
26
+ def self.message(*args, &block)
27
+ ins, outs = *process_devices(args)
28
+ MicroMIDI::Context.new(ins, outs, &block)
29
+ end
30
+ class << self
31
+ alias_method :using, :message
32
+ end
33
+
34
+ module IO
35
+
36
+ def self.new(*args)
37
+ MicroMIDI.message(*args)
38
+ end
39
+
40
+ end
41
+
42
+ private
43
+
44
+ def self.process_devices(args)
45
+ ins = args.find_all { |d| d.respond_to?(:type) && d.type == :input && d.respond_to?(:gets) }
46
+ outs = args.find_all { |d| d.respond_to?(:puts) }
47
+ [ins, outs]
48
+ end
49
+
50
+ end
51
+ MIDI = MicroMIDI
52
+
53
+ # modules
54
+ require 'micromidi/instructions/composite'
55
+
56
+ # classes
57
+ require 'micromidi/context'
58
+ require 'micromidi/state'
59
+ require 'micromidi/instructions/process'
60
+ require 'micromidi/instructions/input'
61
+ require 'micromidi/instructions/message'
62
+ require 'micromidi/instructions/output'
63
+ require 'micromidi/instructions/sticky'
64
+ require 'micromidi/instructions/sysex'
65
+
66
+ # re-open
67
+ require 'micromidi/instructions/shorthand'
@@ -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