midiator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/chromatic_scale.rb +33 -0
- data/examples/metronome.rb +75 -0
- data/lib/midiator.rb +32 -0
- data/lib/midiator/driver.rb +100 -0
- data/lib/midiator/driver_registry.rb +53 -0
- data/lib/midiator/drivers/alsa.rb +52 -0
- data/lib/midiator/drivers/core_midi.rb +82 -0
- data/lib/midiator/drivers/winmm.rb +49 -0
- data/lib/midiator/drums.rb +39 -0
- data/lib/midiator/exceptions.rb +18 -0
- data/lib/midiator/interface.rb +90 -0
- data/lib/midiator/notes.rb +93 -0
- data/lib/midiator/timer.rb +62 -0
- data/lib/string_extensions.rb +59 -0
- data/misc/rake/packaging.rb +50 -0
- data/misc/rake/rdoc.rb +45 -0
- data/misc/rake/testing.rb +32 -0
- data/spec/driver_registry_spec.rb +115 -0
- data/spec/driver_spec.rb +29 -0
- data/spec/interface_spec.rb +128 -0
- data/spec/string_extensions_spec.rb +35 -0
- metadata +76 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# A simple example that plays the chromatic scale from C4 to C6 and back
|
4
|
+
# again. Also shows how to use the MIDIator::Notes mixin.
|
5
|
+
#
|
6
|
+
# == Authors
|
7
|
+
#
|
8
|
+
# * Ben Bleything <ben@bleything.net>
|
9
|
+
#
|
10
|
+
# == Copyright
|
11
|
+
#
|
12
|
+
# Copyright (c) 2008 Ben Bleything
|
13
|
+
#
|
14
|
+
# This code released under the terms of the MIT license.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'midiator'
|
18
|
+
|
19
|
+
midi = MIDIator::Interface.new
|
20
|
+
midi.autodetect_driver
|
21
|
+
|
22
|
+
include MIDIator::Notes
|
23
|
+
|
24
|
+
scale = [ C4, Cs4, D4, Eb4, E4, F4, Fs4, G4, Gs4, A4, Bb4, B4,
|
25
|
+
C5, Cs5, D5, Eb5, E5, F5, Fs5, G5, Gs5, A5, Bb5, B5 ]
|
26
|
+
|
27
|
+
scale.each do |note|
|
28
|
+
midi.play note
|
29
|
+
end
|
30
|
+
|
31
|
+
scale.reverse.each do |note|
|
32
|
+
midi.play note
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# A metronome that can run at any given tempo.
|
4
|
+
#
|
5
|
+
# == Synopsis
|
6
|
+
#
|
7
|
+
# $ ./metronome
|
8
|
+
# => starts a metronome running at 120bpm
|
9
|
+
#
|
10
|
+
# $ ./metronome 180
|
11
|
+
# => starts a metronome running at 180bpm
|
12
|
+
#
|
13
|
+
# == Authors
|
14
|
+
#
|
15
|
+
# * Ben Bleything <ben@bleything.net>
|
16
|
+
#
|
17
|
+
# == Copyright
|
18
|
+
#
|
19
|
+
# Copyright (c) 2008 Ben Bleything
|
20
|
+
#
|
21
|
+
# This code released under the terms of the MIT license.
|
22
|
+
#
|
23
|
+
|
24
|
+
########################################################################
|
25
|
+
### S A N I T Y C H E C K S
|
26
|
+
########################################################################
|
27
|
+
|
28
|
+
@tempo = 120.0
|
29
|
+
|
30
|
+
if input = ARGV[0]
|
31
|
+
begin
|
32
|
+
@tempo = Float( input )
|
33
|
+
rescue ArgumentError => e
|
34
|
+
$stderr.puts "'#{input}' is not a valid tempo.\n"
|
35
|
+
$stderr.puts "Please specify the tempo in beats per minute " +
|
36
|
+
"(bpm). Fractional values are allowed!"
|
37
|
+
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
########################################################################
|
44
|
+
### M I D I A T O R S E T U P
|
45
|
+
########################################################################
|
46
|
+
|
47
|
+
require 'midiator'
|
48
|
+
include MIDIator::Notes
|
49
|
+
|
50
|
+
@midi = MIDIator::Interface.new
|
51
|
+
@midi.autodetect_driver
|
52
|
+
@midi.program_change 0, 115 # Wood block!
|
53
|
+
|
54
|
+
# trap interrupts to properly kill the timer
|
55
|
+
Signal.trap( "INT" ) { @timer.thread.exit! }
|
56
|
+
|
57
|
+
########################################################################
|
58
|
+
### T I M E R S E T U P
|
59
|
+
########################################################################
|
60
|
+
|
61
|
+
# 60 / tempo gives us the delay (in seconds) between each beat. Divide
|
62
|
+
# that again by 10 to give us our desired resolution.
|
63
|
+
@interval = 60.0 / @tempo
|
64
|
+
@timer = MIDIator::Timer.new( @interval / 10 )
|
65
|
+
|
66
|
+
def register_next_bang( time )
|
67
|
+
@timer.at( time ) do |yielded_time|
|
68
|
+
register_next_bang yielded_time + @interval
|
69
|
+
@midi.play MiddleC
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "Starting metronome running at #{@tempo} bpm."
|
74
|
+
register_next_bang Time.now.to_f
|
75
|
+
@timer.thread.join
|
data/lib/midiator.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# A Ruby library for interacting with system-level MIDI services.
|
4
|
+
#
|
5
|
+
# == Authors
|
6
|
+
#
|
7
|
+
# * Ben Bleything <ben@bleything.net>
|
8
|
+
#
|
9
|
+
# == Copyright
|
10
|
+
#
|
11
|
+
# Copyright (c) 2008 Ben Bleything
|
12
|
+
#
|
13
|
+
# This code released under the terms of the MIT license.
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'string_extensions'
|
17
|
+
|
18
|
+
module MIDIator
|
19
|
+
VERSION = "0.1.0"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'midiator/driver'
|
23
|
+
require 'midiator/driver_registry'
|
24
|
+
require 'midiator/exceptions'
|
25
|
+
require 'midiator/interface'
|
26
|
+
require 'midiator/timer'
|
27
|
+
|
28
|
+
##########################################################################
|
29
|
+
### S H O R T C U T M O D U L E S
|
30
|
+
##########################################################################
|
31
|
+
require 'midiator/drums'
|
32
|
+
require 'midiator/notes'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The abstractish superclass of all MIDIator MIDI drivers.
|
4
|
+
#
|
5
|
+
# == Authors
|
6
|
+
#
|
7
|
+
# * Ben Bleything <ben@bleything.net>
|
8
|
+
#
|
9
|
+
# == Copyright
|
10
|
+
#
|
11
|
+
# Copyright (c) 2008 Ben Bleything
|
12
|
+
#
|
13
|
+
# This code released under the terms of the MIT license.
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'midiator'
|
17
|
+
require 'midiator/driver_registry'
|
18
|
+
|
19
|
+
class MIDIator::Driver
|
20
|
+
|
21
|
+
##########################################################################
|
22
|
+
### M I D I C O M M A N D C O N S T A N T S
|
23
|
+
##########################################################################
|
24
|
+
|
25
|
+
# Note on
|
26
|
+
ON = 0x90
|
27
|
+
|
28
|
+
# Note off
|
29
|
+
OFF = 0x80
|
30
|
+
|
31
|
+
# Program change
|
32
|
+
PC = 0xc0
|
33
|
+
|
34
|
+
##########################################################################
|
35
|
+
### M A G I C H O O K S
|
36
|
+
##########################################################################
|
37
|
+
|
38
|
+
### Auto-registers subclasses of MIDIator::Driver with the driver registry.
|
39
|
+
def self::inherited( driver_class )
|
40
|
+
driver_name = driver_class.to_s.underscore
|
41
|
+
MIDIator::DriverRegistry.instance.register( driver_name, driver_class )
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
##########################################################################
|
46
|
+
### I N T E R F A C E A P I
|
47
|
+
##########################################################################
|
48
|
+
# These methods are the same across all drivers and are the interface that
|
49
|
+
# MIDIator::Interface interacts with.
|
50
|
+
##########################################################################
|
51
|
+
|
52
|
+
### Do any pre-open setup necessary. Often will not be overridden.
|
53
|
+
def initialize
|
54
|
+
open
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
### Shortcut to send a note_on message.
|
59
|
+
def note_on( note, channel, velocity )
|
60
|
+
message( ON | channel, note, velocity )
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
### Shortcut to send a note_off message.
|
65
|
+
def note_off( note, channel, velocity )
|
66
|
+
message( OFF | channel, note, velocity )
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
### Shortcut to send a program_change message.
|
71
|
+
def program_change( channel, program )
|
72
|
+
message( PC | channel, program )
|
73
|
+
end
|
74
|
+
|
75
|
+
##########################################################################
|
76
|
+
### D R I V E R A P I
|
77
|
+
##########################################################################
|
78
|
+
# subclasses must implement these methods.
|
79
|
+
##########################################################################
|
80
|
+
protected
|
81
|
+
##########################################################################
|
82
|
+
|
83
|
+
### Open the channel to the MIDI service.
|
84
|
+
def open
|
85
|
+
raise NotImplementedError, "You must implement #open in your driver."
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
### Close the channel to the MIDI service.
|
90
|
+
def close
|
91
|
+
raise NotImplementedError, "You must implement #close in your driver."
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
### Send MIDI message to the MIDI service.
|
96
|
+
def message( *args )
|
97
|
+
raise NotImplementedError, "You must implement #message in your driver."
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# A hash-like register of all loaded drivers.
|
4
|
+
#
|
5
|
+
# == Authors
|
6
|
+
#
|
7
|
+
# * Ben Bleything <ben@bleything.net>
|
8
|
+
#
|
9
|
+
# == Copyright
|
10
|
+
#
|
11
|
+
# Copyright (c) 2008 Ben Bleything
|
12
|
+
#
|
13
|
+
# This code released under the terms of the MIT license.
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'singleton'
|
17
|
+
require 'midiator'
|
18
|
+
|
19
|
+
class MIDIator::DriverRegistry
|
20
|
+
include Singleton
|
21
|
+
|
22
|
+
### Stores the given +klass+ in the <tt>@drivers</tt> hash, keyed by +name+.
|
23
|
+
### Typically called via MIDIator::Driver's +inherited+ hook.
|
24
|
+
def register_driver( name, klass )
|
25
|
+
@drivers ||= {}
|
26
|
+
|
27
|
+
raise ArgumentError, "Attempted to register something that is not a MIDIator::Driver" unless
|
28
|
+
klass < MIDIator::Driver
|
29
|
+
|
30
|
+
@drivers.each do |existing_name, existing_klass|
|
31
|
+
raise ArgumentError, "Already registered #{existing_klass.to_s} as '#{existing_name}'." if
|
32
|
+
existing_klass == klass
|
33
|
+
end
|
34
|
+
|
35
|
+
@drivers[ name ] = klass
|
36
|
+
end
|
37
|
+
alias register register_driver
|
38
|
+
alias << register_driver
|
39
|
+
|
40
|
+
|
41
|
+
### Returns the number of drivers currently registered.
|
42
|
+
def size
|
43
|
+
return @drivers.size
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
### Included to make the registry quack like a hash. Delegates to the
|
48
|
+
### <tt>@drivers</tt> hash.
|
49
|
+
def []( name )
|
50
|
+
return @drivers[ name ]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The MIDIator driver to interact with ALSA on Linux. Taken more or less
|
4
|
+
# directly from Practical Ruby Projects.
|
5
|
+
#
|
6
|
+
# NOTE: as yet completely untested.
|
7
|
+
#
|
8
|
+
# == Authors
|
9
|
+
#
|
10
|
+
# * Topher Cyll
|
11
|
+
# * Ben Bleything <ben@bleything.net>
|
12
|
+
#
|
13
|
+
# == Copyright
|
14
|
+
#
|
15
|
+
# Copyright (c) 2008 Topher Cyll
|
16
|
+
#
|
17
|
+
# This code released under the terms of the MIT license.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'dl/import'
|
21
|
+
|
22
|
+
require 'midiator'
|
23
|
+
require 'midiator/driver'
|
24
|
+
require 'midiator/driver_registry'
|
25
|
+
|
26
|
+
class MIDIator::Driver::ALSA < MIDIator::Driver # :nodoc:
|
27
|
+
module C # :nodoc:
|
28
|
+
extend DL::Importable
|
29
|
+
dlload 'libasound.so'
|
30
|
+
|
31
|
+
extern "int snd_rawmidi_open(void*, void*, char*, int)"
|
32
|
+
extern "int snd_rawmidi_close(void*)"
|
33
|
+
extern "int snd_rawmidi_write(void*, void*, int)"
|
34
|
+
extern "int snd_rawmidi_drain(void*)"
|
35
|
+
end
|
36
|
+
|
37
|
+
def open
|
38
|
+
@output = DL::PtrData.new(nil)
|
39
|
+
C.snd_rawmidi_open(nil, @output.ref, "virtual", 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
def close
|
43
|
+
C.snd_rawmidi_close(@output)
|
44
|
+
end
|
45
|
+
|
46
|
+
def message(*args)
|
47
|
+
format = "C" * args.size
|
48
|
+
bytes = args.pack(format).to_ptr
|
49
|
+
C.snd_rawmidi_write(@output, bytes, args.size)
|
50
|
+
C.snd_rawmidi_drain(@output)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The MIDIator driver to interact with OSX's CoreMIDI. Taken more or less
|
4
|
+
# directly from Practical Ruby Projects.
|
5
|
+
#
|
6
|
+
# == Authors
|
7
|
+
#
|
8
|
+
# * Topher Cyll
|
9
|
+
# * Ben Bleything <ben@bleything.net>
|
10
|
+
#
|
11
|
+
# == Copyright
|
12
|
+
#
|
13
|
+
# Copyright (c) 2008 Topher Cyll
|
14
|
+
#
|
15
|
+
# This code released under the terms of the MIT license.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'dl/import'
|
19
|
+
|
20
|
+
require 'midiator'
|
21
|
+
require 'midiator/driver'
|
22
|
+
require 'midiator/driver_registry'
|
23
|
+
|
24
|
+
class MIDIator::Driver::CoreMIDI < MIDIator::Driver # :nodoc:
|
25
|
+
##########################################################################
|
26
|
+
### S Y S T E M I N T E R F A C E
|
27
|
+
##########################################################################
|
28
|
+
module C # :nodoc:
|
29
|
+
extend DL::Importable
|
30
|
+
dlload '/System/Library/Frameworks/CoreMIDI.framework/Versions/Current/CoreMIDI'
|
31
|
+
|
32
|
+
extern "int MIDIClientCreate( void*, void*, void*, void* )"
|
33
|
+
extern "int MIDIClientDispose( void* )"
|
34
|
+
extern "int MIDIGetNumberOfDestinations()"
|
35
|
+
extern "void* MIDIGetDestination( int )"
|
36
|
+
extern "int MIDIOutputPortCreate( void*, void*, void* )"
|
37
|
+
extern "void* MIDIPacketListInit( void* )"
|
38
|
+
extern "void* MIDIPacketListAdd( void*, int, void*, int, int, int, void* )"
|
39
|
+
extern "int MIDISend( void*, void*, void* )"
|
40
|
+
end
|
41
|
+
|
42
|
+
module CF # :nodoc:
|
43
|
+
extend DL::Importable
|
44
|
+
dlload '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation'
|
45
|
+
|
46
|
+
extern "void* CFStringCreateWithCString( void*, char*, int )"
|
47
|
+
end
|
48
|
+
|
49
|
+
##########################################################################
|
50
|
+
### D R I V E R A P I
|
51
|
+
##########################################################################
|
52
|
+
|
53
|
+
def open
|
54
|
+
client_name = CF.cFStringCreateWithCString( nil, "RubyMIDI", 0 )
|
55
|
+
@client = DL::PtrData.new( nil )
|
56
|
+
C.mIDIClientCreate( client_name, nil, nil, @client.ref )
|
57
|
+
|
58
|
+
port_name = CF.cFStringCreateWithCString( nil, "Output", 0 )
|
59
|
+
@outport = DL::PtrData.new( nil )
|
60
|
+
C.mIDIOutputPortCreate( @client, port_name, @outport.ref )
|
61
|
+
|
62
|
+
number_of_destinations = C.mIDIGetNumberOfDestinations()
|
63
|
+
raise NoMIDIDestinations if number_of_destinations < 1
|
64
|
+
@destination = C.mIDIGetDestination( @midi_destination )
|
65
|
+
end
|
66
|
+
|
67
|
+
def close
|
68
|
+
C.mIDIClientDispose( @client )
|
69
|
+
end
|
70
|
+
|
71
|
+
def message( *args )
|
72
|
+
format = "C" * args.size
|
73
|
+
bytes = args.pack( format ).to_ptr
|
74
|
+
packet_list = DL.malloc( 256 )
|
75
|
+
packet_ptr = C.mIDIPacketListInit( packet_list )
|
76
|
+
|
77
|
+
# Pass in two 32-bit 0s for the 64 bit time
|
78
|
+
packet_ptr = C.mIDIPacketListAdd( packet_list, 256, packet_ptr, 0, 0, args.size, bytes )
|
79
|
+
|
80
|
+
C.mIDISend( @outport, @destination, packet_list )
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The MIDIator driver to interact with Windows Multimedia. Taken more or less
|
4
|
+
# directly from Practical Ruby Projects.
|
5
|
+
#
|
6
|
+
# NOTE: as yet completely untested.
|
7
|
+
#
|
8
|
+
# == Authors
|
9
|
+
#
|
10
|
+
# * Topher Cyll
|
11
|
+
# * Ben Bleything <ben@bleything.net>
|
12
|
+
#
|
13
|
+
# == Copyright
|
14
|
+
#
|
15
|
+
# Copyright (c) 2008 Topher Cyll
|
16
|
+
#
|
17
|
+
# This code released under the terms of the MIT license.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'dl/import'
|
21
|
+
|
22
|
+
require 'midiator'
|
23
|
+
require 'midiator/driver'
|
24
|
+
require 'midiator/driver_registry'
|
25
|
+
|
26
|
+
class MIDIator::Driver::WinMM < MIDIator::Driver # :nodoc:
|
27
|
+
module C # :nodoc:
|
28
|
+
extend DL::Importable
|
29
|
+
dlload 'winmm'
|
30
|
+
|
31
|
+
extern "int midiOutOpen(HMIDIOUT*, int, int, int, int)"
|
32
|
+
extern "int midiOutClose(int)"
|
33
|
+
extern "int midiOutShortMsg(int, int)"
|
34
|
+
end
|
35
|
+
|
36
|
+
def open
|
37
|
+
@device = DL.malloc(DL.sizeof('I'))
|
38
|
+
C.midiOutOpen(@device, -1, 0, 0, 0)
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
C.midiOutClose(@device.ptr.to_i)
|
43
|
+
end
|
44
|
+
|
45
|
+
def message(one, two=0, three=0)
|
46
|
+
message = one + (two << 8) + (three << 16)
|
47
|
+
C.midiOutShortMsg(@device.ptr.to_i, message)
|
48
|
+
end
|
49
|
+
end
|