midiator 0.1.0
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/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
|