midi 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.
@@ -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