midi-topaz 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +13 -0
- data/README.rdoc +96 -0
- data/lib/topaz/external_midi_tempo.rb +81 -0
- data/lib/topaz/internal_tempo.rb +59 -0
- data/lib/topaz/midi_sync_output.rb +28 -0
- data/lib/topaz/tempo.rb +101 -0
- data/lib/topaz/tempo_calculator.rb +38 -0
- data/lib/topaz.rb +22 -0
- data/test/config.rb +13 -0
- data/test/helper.rb +14 -0
- data/test/test_internal_tempo.rb +28 -0
- metadata +110 -0
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,96 @@
|
|
1
|
+
= topaz
|
2
|
+
|
3
|
+
{pic}[http://images.treetrouble.net/images/topaz.jpg]
|
4
|
+
|
5
|
+
MIDI syncable tempo in Ruby
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
gem install midi-topaz
|
10
|
+
|
11
|
+
== Requirements
|
12
|
+
|
13
|
+
* {gamelan}[http://github.com/jvoorhis/gamelan]
|
14
|
+
* {midi-eye}[http://github.com/arirusso/midi-eye]
|
15
|
+
* {midi-message}[http://github.com/arirusso/midi-message]
|
16
|
+
* {unimidi}[http://github.com/arirusso/unimidi]
|
17
|
+
|
18
|
+
these will install automatically with the gem
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
require "topaz"
|
23
|
+
|
24
|
+
For demonstration purposes, here's a mock sequencer class and object
|
25
|
+
|
26
|
+
class Sequencer
|
27
|
+
|
28
|
+
def step
|
29
|
+
@i ||= 0
|
30
|
+
$stdout.puts "step #{@i+=1}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
seq = Sequencer.new
|
36
|
+
|
37
|
+
The simplest application of Topaz is to create a clock to step that sequencer at a given rate. Using timing generated internally by your computer, the passed in block will be called repeatedly at 130 BPM
|
38
|
+
|
39
|
+
@tempo = Topaz::Tempo.new(130) { seq.step }
|
40
|
+
|
41
|
+
You may also use another MIDI device to generate timing and control the tempo. The unimidi input to which that device is connected can be passed to the Tempo constructor
|
42
|
+
|
43
|
+
@input = UniMIDI::Input.first.open # an midi input
|
44
|
+
|
45
|
+
@tempo = Topaz::Tempo.new(:midi => @input) { seq.step }
|
46
|
+
|
47
|
+
Topaz can also act as a master clock. If a MIDI output is passed to Topaz, MIDI start, stop and clock signals will automatically be sent to that output at the appropriate time
|
48
|
+
|
49
|
+
@output = UniMIDI::Output.first.open # a midi output
|
50
|
+
|
51
|
+
@tempo = Topaz::Tempo.new(120, :midi => @output) { seq.step }
|
52
|
+
|
53
|
+
Input and multiple outputs can be used simultaneously
|
54
|
+
|
55
|
+
@tempo = Topaz::Tempo.new(:midi => [@input, @output1, @output2]) { seq.step }
|
56
|
+
|
57
|
+
Once the Tempo object is initialized, start the clock
|
58
|
+
|
59
|
+
@tempo.start
|
60
|
+
|
61
|
+
If you are syncing to external clock, nothing will happen until a "start" or "clock" message is received
|
62
|
+
|
63
|
+
==== Other things to note
|
64
|
+
|
65
|
+
Whether or not you are using an internal or external clock source, the event block will be called at quarter note intervals by default. If you wish to change this set the option :interval. In this case, 16th notes will occur at 138 BPM
|
66
|
+
|
67
|
+
@tempo = Topaz::Tempo.new(138, :interval => 16) { seq.step }
|
68
|
+
|
69
|
+
View the current tempo, which is calculated by Topaz if you're using an external MIDI source.
|
70
|
+
(this feature is currently in-progress and may return some low values - 5/26/2011)
|
71
|
+
|
72
|
+
@tempo.tempo
|
73
|
+
=> 132.422000
|
74
|
+
|
75
|
+
Run the generator in a background thread by passing :background => true to Tempo#start
|
76
|
+
|
77
|
+
@tempo.start(:background => true)
|
78
|
+
|
79
|
+
Pass in a block that will stop the clock when it evaluates to true
|
80
|
+
|
81
|
+
@tempo.stop_when { @i.eql?(20) }
|
82
|
+
|
83
|
+
== Documentation
|
84
|
+
|
85
|
+
* {examples}[http://github.com/arirusso/topaz/tree/master/examples]
|
86
|
+
* {rdoc}[http://rdoc.info/gems/midi-topaz]
|
87
|
+
|
88
|
+
== Author
|
89
|
+
|
90
|
+
* {Ari Russo}[http://github.com/arirusso] <ari.russo at gmail.com>
|
91
|
+
|
92
|
+
== License
|
93
|
+
|
94
|
+
Apache 2.0, See the file LICENSE
|
95
|
+
|
96
|
+
Copyright (c) 2011 Ari Russo
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module Topaz
|
3
|
+
|
4
|
+
# trigger an event based on received midi clock messages
|
5
|
+
class ExternalMIDITempo
|
6
|
+
|
7
|
+
attr_accessor :action
|
8
|
+
attr_reader :clock
|
9
|
+
|
10
|
+
def initialize(input, options = {})
|
11
|
+
@action = options[:action]
|
12
|
+
self.interval = options[:interval] || 4
|
13
|
+
@tempo_calculator = TempoCalculator.new
|
14
|
+
@clock = MIDIEye::Listener.new(input)
|
15
|
+
|
16
|
+
initialize_clock
|
17
|
+
end
|
18
|
+
|
19
|
+
# this will return a calculated tempo
|
20
|
+
def tempo
|
21
|
+
@tempo_calculator.find_tempo
|
22
|
+
end
|
23
|
+
|
24
|
+
def start(*a)
|
25
|
+
@action[:on_start].call unless @action[:on_start].nil?
|
26
|
+
@clock.start(*a)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop(*a)
|
30
|
+
@action[:on_stop].call unless @action[:on_stop].nil?
|
31
|
+
@clock.start(*a)
|
32
|
+
end
|
33
|
+
|
34
|
+
def join
|
35
|
+
@clock.join
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# change the clock interval
|
40
|
+
# defaults to click once every 24 ticks or one quarter note which is the MIDI standard.
|
41
|
+
# however, if you wish to fire the on_tick event twice as often
|
42
|
+
# (or once per 12 clicks), pass 8
|
43
|
+
#
|
44
|
+
# 1 = whole note
|
45
|
+
# 2 = half note
|
46
|
+
# 4 = quarter note
|
47
|
+
# 6 = dotted quarter
|
48
|
+
# 8 = eighth note
|
49
|
+
# 16 = sixteenth note
|
50
|
+
# etc
|
51
|
+
#
|
52
|
+
def interval=(val)
|
53
|
+
per_qn = val / 4
|
54
|
+
@per_tick = 24 / per_qn
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def initialize_clock
|
60
|
+
@counter = 0
|
61
|
+
@clock.listen_for(:name => "Clock") do |msg|
|
62
|
+
if !@action[:stop_when].nil? && @action[:stop_when].call
|
63
|
+
stop
|
64
|
+
return
|
65
|
+
end
|
66
|
+
@action[:destinations].each do |output|
|
67
|
+
output.on_tick
|
68
|
+
end
|
69
|
+
@tempo_calculator.timestamps << msg[:timestamp]
|
70
|
+
if @counter.eql?(@per_tick)
|
71
|
+
@action[:on_tick].call
|
72
|
+
@counter = 0
|
73
|
+
else
|
74
|
+
@counter += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module Topaz
|
3
|
+
|
4
|
+
class InternalTempo < Gamelan::Timer
|
5
|
+
|
6
|
+
attr_accessor :action
|
7
|
+
|
8
|
+
def initialize(tempo, options = {})
|
9
|
+
@action = options[:action]
|
10
|
+
self.interval = options[:interval] || 4
|
11
|
+
@destinations = options[:destinations]
|
12
|
+
@last = 0
|
13
|
+
@last_sync = 0
|
14
|
+
super({:tempo => tempo})
|
15
|
+
end
|
16
|
+
|
17
|
+
# start the internal timer
|
18
|
+
# pass :background => true to keep the timer in a background thread
|
19
|
+
def start(options = {})
|
20
|
+
@action[:on_start].call unless @action[:on_start].nil?
|
21
|
+
run
|
22
|
+
join unless options[:background]
|
23
|
+
end
|
24
|
+
|
25
|
+
# change the timer's click interval
|
26
|
+
def interval=(val)
|
27
|
+
@interval = val / 4
|
28
|
+
end
|
29
|
+
|
30
|
+
# stop the timer
|
31
|
+
def stop(*a)
|
32
|
+
@action[:on_stop].call unless @action[:on_stop].nil?
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Run all ready tasks.
|
39
|
+
def dispatch
|
40
|
+
# stuff to do on every tick
|
41
|
+
unless @last_sync.eql?((@phase * 24).to_i)
|
42
|
+
# look for stop
|
43
|
+
if !@action[:stop_when].nil? && @action[:stop_when].call
|
44
|
+
stop
|
45
|
+
return
|
46
|
+
end
|
47
|
+
@action[:destinations].each { |dest| dest.on_tick }
|
48
|
+
@last_sync = (@phase * 24).to_i
|
49
|
+
end
|
50
|
+
# stuff to do on @interval
|
51
|
+
unless @last.eql?((@phase * @interval).to_i)
|
52
|
+
@action[:on_tick].call
|
53
|
+
@last = (@phase * @interval).to_i
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module Topaz
|
3
|
+
|
4
|
+
# send sync messages via MIDI
|
5
|
+
class MIDISyncOutput
|
6
|
+
|
7
|
+
def initialize(output, options = {})
|
8
|
+
@output = output
|
9
|
+
end
|
10
|
+
|
11
|
+
# send a start message
|
12
|
+
def on_start
|
13
|
+
@output.puts(MIDIMessage::SystemRealtime["Start"].new.to_a)
|
14
|
+
end
|
15
|
+
|
16
|
+
# send a stop message
|
17
|
+
def on_stop
|
18
|
+
@output.puts(MIDIMessage::SystemRealtime["Stop"].new.to_a)
|
19
|
+
end
|
20
|
+
|
21
|
+
# send a clock message
|
22
|
+
def on_tick
|
23
|
+
@output.puts(MIDIMessage::SystemRealtime["Clock"].new.to_a)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/topaz/tempo.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module Topaz
|
3
|
+
|
4
|
+
# main tempo class
|
5
|
+
class Tempo
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
def_delegators :source, :tempo, :interval, :interval=, :join
|
12
|
+
|
13
|
+
def initialize(*args, &event)
|
14
|
+
@destinations = []
|
15
|
+
|
16
|
+
if args.first.kind_of?(Numeric)
|
17
|
+
@source = InternalTempo.new(args.shift)
|
18
|
+
end
|
19
|
+
options = args.first
|
20
|
+
|
21
|
+
initialize_midi_io(options)
|
22
|
+
raise "You must specify an internal tempo rate or an external tempo source" if @source.nil?
|
23
|
+
@source.action = {
|
24
|
+
:on_start => nil,
|
25
|
+
:on_stop => nil,
|
26
|
+
:on_tick => event,
|
27
|
+
:destinations => @destinations,
|
28
|
+
:stop_when => nil
|
29
|
+
}
|
30
|
+
@source.interval = options[:interval] unless options.nil? || options[:interval].nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
# this will change the tempo
|
34
|
+
#
|
35
|
+
# be warned, in the case that external midi tempo is being used, this will switch to internal
|
36
|
+
# tempo at the desired rate
|
37
|
+
#
|
38
|
+
def tempo=(val)
|
39
|
+
if @source.respond_to?(:tempo=)
|
40
|
+
@source.tempo = val
|
41
|
+
else
|
42
|
+
@source = InternalTempo.new(tempo, @action)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# pass in a callback that is called when start is called
|
47
|
+
def on_start(&block)
|
48
|
+
@source.action[:on_start] = block
|
49
|
+
end
|
50
|
+
|
51
|
+
# pass in a callback that is called when stop is called
|
52
|
+
def on_stop(&block)
|
53
|
+
@source.action[:on_stop] = block
|
54
|
+
end
|
55
|
+
|
56
|
+
# pass in a callback which will
|
57
|
+
def stop_when(&block)
|
58
|
+
@source.action[:stop_when] = block
|
59
|
+
end
|
60
|
+
|
61
|
+
# this will start the generator
|
62
|
+
#
|
63
|
+
# in the case that external midi tempo is being used, this will wait for a start
|
64
|
+
# or clock message
|
65
|
+
#
|
66
|
+
def start(options = {})
|
67
|
+
@start_time = Time.now
|
68
|
+
@destinations.each { |dest| dest.on_start }
|
69
|
+
@source.start(options)
|
70
|
+
end
|
71
|
+
|
72
|
+
# this will stop tempo
|
73
|
+
def stop(options = {})
|
74
|
+
@destinations.each { |dest| dest.on_stop }
|
75
|
+
@source.stop(options)
|
76
|
+
end
|
77
|
+
|
78
|
+
# seconds since start was called
|
79
|
+
def start_time
|
80
|
+
@start_time.nil? ? nil : (Time.now - @start_time).to_f
|
81
|
+
end
|
82
|
+
alias_method :time_since_start, :start_time
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def initialize_midi_io(args)
|
87
|
+
ports = args.kind_of?(Hash) ? args[:midi] : args
|
88
|
+
unless ports.nil?
|
89
|
+
if ports.kind_of?(Array)
|
90
|
+
ports.each { |port| initialize_midi_io(port) }
|
91
|
+
elsif ports.type.eql?(:input) && @source.nil?
|
92
|
+
@source = ExternalMIDITempo.new(ports)
|
93
|
+
elsif ports.type.eql?(:output)
|
94
|
+
@destinations << MIDISyncOutput.new(ports)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module Topaz
|
3
|
+
|
4
|
+
class TempoCalculator
|
5
|
+
|
6
|
+
attr_reader :tempo, :timestamps
|
7
|
+
|
8
|
+
def initialize(tempo = nil)
|
9
|
+
@tempo = tempo
|
10
|
+
@timestamps = []
|
11
|
+
@counter = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
# analyse the tempo based on the last 6 ticks
|
15
|
+
def find_tempo
|
16
|
+
tempo = nil
|
17
|
+
diffs = []
|
18
|
+
@timestamps.shift while @timestamps.length > 6
|
19
|
+
@timestamps.each_with_index { |n, i| (diffs << (@timestamps[i+1] - n)) unless @timestamps[i+1].nil? }
|
20
|
+
unless diffs.empty?
|
21
|
+
avg = (diffs.inject { |a, b| a + b }.to_f / diffs.length.to_f)
|
22
|
+
tempo = ppq24_millis_to_bpm(avg)
|
23
|
+
end
|
24
|
+
@tempo = tempo
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# convert the raw tick intervals to bpm
|
30
|
+
def ppq24_millis_to_bpm(ppq24)
|
31
|
+
quarter_note = (ppq24 * 24.to_f)
|
32
|
+
minute = (60 * 1000) # one minute in millis
|
33
|
+
minute/quarter_note
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/topaz.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# MIDI syncable tempo module in Ruby
|
4
|
+
# (c)2011 Ari Russo and licensed under the Apache 2.0 License
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'forwardable'
|
8
|
+
require 'gamelan'
|
9
|
+
require 'midi-eye'
|
10
|
+
require 'midi-message'
|
11
|
+
|
12
|
+
require 'topaz/external_midi_tempo'
|
13
|
+
require 'topaz/internal_tempo'
|
14
|
+
require 'topaz/midi_sync_output'
|
15
|
+
require 'topaz/tempo_calculator'
|
16
|
+
require 'topaz/tempo'
|
17
|
+
|
18
|
+
module Topaz
|
19
|
+
|
20
|
+
VERSION = "0.0.1"
|
21
|
+
|
22
|
+
end
|
data/test/config.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module TestHelper::Config
|
4
|
+
|
5
|
+
include UniMIDI
|
6
|
+
|
7
|
+
# adjust these constants to suit your hardware configuration
|
8
|
+
# before running tests
|
9
|
+
|
10
|
+
TestInput = Input.first # this is the device you wish to use to test input
|
11
|
+
TestOutput = Output.first # likewise for output
|
12
|
+
|
13
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
|
5
|
+
class InternalTempoTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Topaz
|
8
|
+
include TestHelper
|
9
|
+
|
10
|
+
def test_stop_when
|
11
|
+
i = 0
|
12
|
+
count_to = 5
|
13
|
+
|
14
|
+
tempo = Tempo.new(120) { i += 1 }
|
15
|
+
tempo.stop_when { i.eql?(count_to) }
|
16
|
+
tempo.start
|
17
|
+
|
18
|
+
assert_equal(count_to, i)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_timer
|
22
|
+
i = 0
|
23
|
+
count_to = 5
|
24
|
+
|
25
|
+
tempo = Tempo.new(60) { i += 1 }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: midi-topaz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ari Russo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-26 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: gamelan
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: midi-message
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.0.4
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: midi-eye
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.0.7
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: unimidi
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 0.1.10
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
description: MIDI syncable tempo in Ruby
|
61
|
+
email:
|
62
|
+
- ari.russo@gmail.com
|
63
|
+
executables: []
|
64
|
+
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
extra_rdoc_files: []
|
68
|
+
|
69
|
+
files:
|
70
|
+
- lib/topaz.rb
|
71
|
+
- lib/topaz/midi_sync_output.rb
|
72
|
+
- lib/topaz/internal_tempo.rb
|
73
|
+
- lib/topaz/tempo_calculator.rb
|
74
|
+
- lib/topaz/external_midi_tempo.rb
|
75
|
+
- lib/topaz/tempo.rb
|
76
|
+
- test/helper.rb
|
77
|
+
- test/config.rb
|
78
|
+
- test/test_internal_tempo.rb
|
79
|
+
- LICENSE
|
80
|
+
- README.rdoc
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: http://github.com/arirusso/topaz
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.6
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project: midi-topaz
|
105
|
+
rubygems_version: 1.6.2
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: MIDI syncable tempo in Ruby
|
109
|
+
test_files: []
|
110
|
+
|