midi-topaz 0.0.15 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +2 -2
- data/{README.rdoc → README.md} +68 -45
- data/lib/topaz/events.rb +44 -0
- data/lib/topaz/external_midi_tempo.rb +13 -15
- data/lib/topaz/internal_tempo.rb +9 -13
- data/lib/topaz/midi_sync_output.rb +13 -11
- data/lib/topaz/tempo.rb +79 -82
- data/lib/topaz/tempo_calculator.rb +8 -7
- data/lib/topaz.rb +15 -14
- data/test/config.rb +4 -6
- data/test/helper.rb +5 -9
- data/test/{test_internal_tempo.rb → internal_tempo_test.rb} +2 -4
- metadata +89 -70
- data/lib/topaz/tempo_source.rb +0 -20
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9451f1766a035db3721179e7deb859f9a1bf9e88
|
4
|
+
data.tar.gz: a4d0c37c97e698f64876210c24ab4039809f5cb1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21c97b8c699007e2e26b3e00db19977b8d465d7765b509487bad9524bbee4aaec30f88d01d688459a4ef0c2653627542ae2e278ed5ba6f8ac1e5b729f7a14956
|
7
|
+
data.tar.gz: 1163165b5d6e5b8308e71cdb6aea313c178c02b90cc9f622b21b47c26f52a2684fb3485f67ae981cdb40155f3733e15ed6be716510ba2160386af1291d94a974
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright 2011 Ari Russo
|
1
|
+
Copyright 2011-2014 Ari Russo
|
2
2
|
|
3
3
|
Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
you may not use this file except in compliance with the License.
|
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
|
|
10
10
|
distributed under the License is distributed on an "AS IS" BASIS,
|
11
11
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
See the License for the specific language governing permissions and
|
13
|
-
limitations under the License.
|
13
|
+
limitations under the License.
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,96 +1,119 @@
|
|
1
|
-
|
1
|
+
# Topaz
|
2
2
|
|
3
|
-
|
3
|
+
![pic](http://img526.imageshack.us/img526/5781/topazt.jpg)
|
4
4
|
|
5
5
|
MIDI syncable tempo in Ruby
|
6
6
|
|
7
|
-
|
7
|
+
## Installation
|
8
8
|
|
9
|
-
|
9
|
+
`gem install midi-topaz`
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
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]
|
11
|
+
or with Bundler, add this to your Gemfile
|
12
|
+
|
13
|
+
`gem "midi-topaz"`
|
17
14
|
|
18
|
-
|
15
|
+
## Usage
|
19
16
|
|
20
|
-
|
17
|
+
```ruby
|
18
|
+
require "topaz"
|
19
|
+
```
|
21
20
|
|
22
|
-
require "topaz"
|
23
|
-
|
24
21
|
For demonstration purposes, here's a mock sequencer class and object
|
25
22
|
|
26
|
-
|
23
|
+
```ruby
|
24
|
+
class Sequencer
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
|
26
|
+
def step
|
27
|
+
@i ||= 0
|
28
|
+
puts "step #{@i+=1}"
|
33
29
|
end
|
30
|
+
|
31
|
+
end
|
34
32
|
|
35
|
-
|
33
|
+
sequencer = Sequencer.new
|
34
|
+
```
|
36
35
|
|
37
36
|
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
37
|
|
39
|
-
|
38
|
+
```ruby
|
39
|
+
@tempo = Topaz::Tempo.new(130) { sequencer.step }
|
40
|
+
```
|
40
41
|
|
41
42
|
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
|
|
43
|
-
|
44
|
+
```ruby
|
45
|
+
@input = UniMIDI::Input.first.open # an midi input
|
44
46
|
|
45
|
-
|
47
|
+
@tempo = Topaz::Tempo.new(@input) { sequencer.step }
|
48
|
+
```
|
46
49
|
|
47
50
|
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
51
|
|
49
|
-
|
50
|
-
|
51
|
-
@tempo = Topaz::Tempo.new(120, :midi => @output) { seq.step }
|
52
|
+
```ruby
|
53
|
+
@output = UniMIDI::Output.first.open # a midi output
|
52
54
|
|
55
|
+
@tempo = Topaz::Tempo.new(120, :midi => @output) do
|
56
|
+
sequencer.step
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
53
60
|
Input and multiple outputs can be used simultaneously
|
54
61
|
|
55
|
-
|
56
|
-
|
62
|
+
```ruby
|
63
|
+
@tempo = Topaz::Tempo.new(@input, :midi => [@output1, @output2]) do
|
64
|
+
sequencer.step
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
57
68
|
Once the Tempo object is initialized, start the clock
|
58
69
|
|
59
|
-
|
60
|
-
|
70
|
+
```ruby
|
71
|
+
@tempo.start
|
72
|
+
```
|
73
|
+
|
61
74
|
If you are syncing to external clock, nothing will happen until a "start" or "clock" message is received
|
62
75
|
|
63
|
-
|
76
|
+
#### Other things to note
|
64
77
|
|
65
78
|
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, the event will be fired 4 times per beat (16th notes) at 138 BPM
|
66
79
|
|
67
|
-
|
80
|
+
```ruby
|
81
|
+
@tempo = Topaz::Tempo.new(138, :interval => 16) do
|
82
|
+
sequencer.step
|
83
|
+
end
|
84
|
+
```
|
68
85
|
|
69
86
|
View the current tempo, which is calculated by Topaz if you're using an external MIDI source.
|
70
87
|
(this feature is currently in-progress and may return some low values - 5/26/2011)
|
71
88
|
|
72
|
-
|
73
|
-
|
74
|
-
|
89
|
+
```ruby
|
90
|
+
@tempo.tempo
|
91
|
+
=> 132.422000
|
92
|
+
```
|
93
|
+
|
75
94
|
Run the generator in a background thread by passing :background => true to Tempo#start
|
76
95
|
|
77
|
-
|
96
|
+
```ruby
|
97
|
+
@tempo.start(:background => true)
|
98
|
+
```
|
78
99
|
|
79
100
|
Pass in a block that will stop the clock when it evaluates to true
|
80
101
|
|
81
|
-
|
102
|
+
```ruby
|
103
|
+
@tempo.stop_when { @i.eql?(20) }
|
104
|
+
```
|
82
105
|
|
83
|
-
|
106
|
+
## Documentation
|
84
107
|
|
85
|
-
*
|
86
|
-
*
|
108
|
+
* [examples](http://github.com/arirusso/topaz/tree/master/examples)
|
109
|
+
* [rdoc](http://rdoc.info/gems/midi-topaz)
|
87
110
|
|
88
|
-
|
111
|
+
## Author
|
89
112
|
|
90
|
-
*
|
113
|
+
* [Ari Russo](http://github.com/arirusso) <ari.russo at gmail.com>
|
91
114
|
|
92
|
-
|
115
|
+
## License
|
93
116
|
|
94
117
|
Apache 2.0, See the file LICENSE
|
95
118
|
|
96
|
-
Copyright (c) 2011 Ari Russo
|
119
|
+
Copyright (c) 2011-2014 Ari Russo
|
data/lib/topaz/events.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Topaz
|
2
|
+
|
3
|
+
# User defined callbacks for tempo events
|
4
|
+
class Events
|
5
|
+
|
6
|
+
attr_reader :tick
|
7
|
+
attr_accessor :midi_clock, :midi_start, :midi_stop, :start, :stop, :stop_when
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@start = []
|
11
|
+
@stop = []
|
12
|
+
@tick = []
|
13
|
+
|
14
|
+
@midi_clock = nil
|
15
|
+
@midi_start = nil
|
16
|
+
@midi_stop = nil
|
17
|
+
@stop_when = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def do_midi_clock
|
21
|
+
@midi_clock.call
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_start
|
25
|
+
@midi_start.call
|
26
|
+
@start.each(&:call)
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_stop
|
30
|
+
@midi_stop.call
|
31
|
+
@stop.each(&:call)
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_tick
|
35
|
+
@tick.each(&:call)
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop?
|
39
|
+
@stop_when.call unless @stop_when.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -1,23 +1,20 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
module Topaz
|
3
2
|
|
4
|
-
#
|
3
|
+
# Trigger an event based on received midi clock messages
|
5
4
|
class ExternalMIDITempo
|
6
|
-
|
7
|
-
include TempoSource
|
8
|
-
|
5
|
+
|
9
6
|
attr_reader :clock
|
10
7
|
|
11
|
-
def initialize(
|
12
|
-
@
|
13
|
-
self.interval = options[:interval] || 4
|
8
|
+
def initialize(events, input, options = {})
|
9
|
+
@events = events
|
14
10
|
@tempo_calculator = TempoCalculator.new
|
15
11
|
@clock = MIDIEye::Listener.new(input)
|
16
|
-
|
12
|
+
self.interval = options[:interval] || 4
|
13
|
+
|
17
14
|
initialize_clock
|
18
15
|
end
|
19
16
|
|
20
|
-
#
|
17
|
+
# This will return a calculated tempo
|
21
18
|
def tempo
|
22
19
|
@tempo_calculator.find_tempo
|
23
20
|
end
|
@@ -56,7 +53,7 @@ module Topaz
|
|
56
53
|
@per_tick = 24 / per_qn
|
57
54
|
end
|
58
55
|
|
59
|
-
#
|
56
|
+
# Return the interval at which the tick event is fired
|
60
57
|
def interval
|
61
58
|
4 * (24 / @per_tick)
|
62
59
|
end
|
@@ -65,12 +62,13 @@ module Topaz
|
|
65
62
|
|
66
63
|
def initialize_clock
|
67
64
|
@counter = 0
|
65
|
+
# Note that this doesn't wait for a start signal
|
68
66
|
@clock.listen_for(:name => "Clock") do |msg|
|
69
|
-
(stop and return) if stop?
|
70
|
-
do_midi_clock
|
67
|
+
(stop and return) if @events.stop?
|
68
|
+
@events.do_midi_clock # thru
|
71
69
|
@tempo_calculator.timestamps << msg[:timestamp]
|
72
70
|
if @counter.eql?(@per_tick)
|
73
|
-
do_tick
|
71
|
+
@events.do_tick
|
74
72
|
@counter = 0
|
75
73
|
else
|
76
74
|
@counter += 1
|
@@ -80,4 +78,4 @@ module Topaz
|
|
80
78
|
|
81
79
|
end
|
82
80
|
|
83
|
-
end
|
81
|
+
end
|
data/lib/topaz/internal_tempo.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
module Topaz
|
3
2
|
|
4
3
|
class InternalTempo < Gamelan::Timer
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :action
|
9
|
-
|
10
|
-
def initialize(actions, tempo, options = {})
|
11
|
-
@actions = actions
|
12
|
-
self.interval = options[:interval] || 4
|
4
|
+
|
5
|
+
def initialize(events, tempo, options = {})
|
6
|
+
@events = events
|
13
7
|
@last = 0
|
14
8
|
@last_sync = 0
|
9
|
+
self.interval = options[:interval] || 4
|
10
|
+
|
15
11
|
super({:tempo => tempo})
|
16
12
|
end
|
17
13
|
|
@@ -19,7 +15,7 @@ module Topaz
|
|
19
15
|
# pass :background => true to keep the timer in a background thread
|
20
16
|
def start(options = {})
|
21
17
|
run
|
22
|
-
join unless options[:background]
|
18
|
+
join unless !!options[:background]
|
23
19
|
self
|
24
20
|
end
|
25
21
|
|
@@ -50,13 +46,13 @@ module Topaz
|
|
50
46
|
# stuff to do on every tick
|
51
47
|
if time_for_midi_clock?
|
52
48
|
# look for stop
|
53
|
-
(stop and return) if stop?
|
54
|
-
do_midi_clock
|
49
|
+
(stop and return) if @events.stop?
|
50
|
+
@events.do_midi_clock
|
55
51
|
@last_sync = (@phase * 24).to_i
|
56
52
|
end
|
57
53
|
# stuff to do on @interval
|
58
54
|
if time_for_tick?
|
59
|
-
do_tick
|
55
|
+
@events.do_tick
|
60
56
|
@last = (@phase * @interval).to_i
|
61
57
|
end
|
62
58
|
end
|
@@ -1,30 +1,32 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
module Topaz
|
3
2
|
|
4
|
-
#
|
3
|
+
# Send clock messages via MIDI
|
5
4
|
class MIDISyncOutput
|
6
5
|
|
7
6
|
attr_reader :output
|
8
7
|
|
9
|
-
def initialize(output
|
8
|
+
def initialize(output)
|
10
9
|
@output = output
|
11
10
|
end
|
12
11
|
|
13
|
-
#
|
12
|
+
# Send a start message
|
14
13
|
def start(*a)
|
15
|
-
|
14
|
+
start = MIDIMessage::SystemRealtime["Start"].new.to_a
|
15
|
+
@output.puts(start)
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
18
|
+
# Send a stop message
|
19
19
|
def stop(*a)
|
20
|
-
|
20
|
+
stop = MIDIMessage::SystemRealtime["Stop"].new.to_a
|
21
|
+
@output.puts(stop)
|
21
22
|
end
|
22
23
|
|
23
|
-
#
|
24
|
-
def
|
25
|
-
|
24
|
+
# Send a clock tick message
|
25
|
+
def clock(*a)
|
26
|
+
clock = MIDIMessage::SystemRealtime["Clock"].new.to_a
|
27
|
+
@output.puts(clock)
|
26
28
|
end
|
27
29
|
|
28
30
|
end
|
29
31
|
|
30
|
-
end
|
32
|
+
end
|
data/lib/topaz/tempo.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
module Topaz
|
3
2
|
|
4
|
-
#
|
3
|
+
# Main tempo class
|
5
4
|
class Tempo
|
6
5
|
|
7
6
|
extend Forwardable
|
@@ -10,96 +9,73 @@ module Topaz
|
|
10
9
|
|
11
10
|
def_delegators :source, :tempo, :interval, :interval=, :join
|
12
11
|
|
13
|
-
def initialize(tempo_or_input, options = {}, &
|
12
|
+
def initialize(tempo_or_input, options = {}, &tick_event)
|
14
13
|
@paused = false
|
15
|
-
@destinations = []
|
16
|
-
@
|
17
|
-
:start => nil,
|
18
|
-
:stop => nil,
|
19
|
-
:tick => nil,
|
20
|
-
:midi_clock => Proc.new { @destinations.each { |d| d.send(:midi_clock) if d.respond_to?(:midi_clock) } },
|
21
|
-
:stop_when => nil
|
22
|
-
}
|
23
|
-
|
24
|
-
on_tick(&event)
|
14
|
+
@destinations = []
|
15
|
+
@events = Events.new
|
25
16
|
|
26
|
-
|
27
|
-
@source = InternalTempo.new(@actions, tempo_or_input)
|
28
|
-
else
|
29
|
-
midi_clock_source = tempo_or_input
|
30
|
-
end
|
31
|
-
|
32
|
-
initialize_midi_io(options[:midi], midi_clock_source)
|
33
|
-
raise "You must specify an internal tempo rate or an external tempo source" if @source.nil?
|
34
|
-
|
35
|
-
@source.interval = options[:interval] unless options[:interval].nil?
|
36
|
-
|
17
|
+
configure(tempo_or_input, options, &tick_event)
|
37
18
|
end
|
38
19
|
|
39
|
-
#
|
20
|
+
# Change the tempo
|
40
21
|
#
|
41
|
-
#
|
42
|
-
# tempo at the desired rate
|
22
|
+
# Caution that in the case that external MIDI tempo is being used, this will switch to internal
|
23
|
+
# tempo at the desired rate.
|
43
24
|
#
|
44
25
|
def tempo=(val)
|
45
26
|
if @source.respond_to?(:tempo=)
|
46
27
|
@source.tempo = val
|
47
28
|
else
|
48
|
-
@source = InternalTempo.new(
|
29
|
+
@source = InternalTempo.new(@events, val)
|
49
30
|
end
|
50
31
|
end
|
51
32
|
|
52
|
-
#
|
33
|
+
# Pause the clock
|
53
34
|
def pause
|
54
35
|
@pause = true
|
55
36
|
end
|
56
37
|
|
57
|
-
#
|
38
|
+
# Unpause the clock
|
58
39
|
def unpause
|
59
40
|
@pause = false
|
60
41
|
end
|
61
42
|
|
62
|
-
#
|
43
|
+
# Is this clock paused?
|
63
44
|
def paused?
|
64
45
|
@pause
|
65
46
|
end
|
66
47
|
|
48
|
+
# Toggle pausing the clock
|
67
49
|
def toggle_pause
|
68
50
|
paused? ? unpause : pause
|
69
51
|
end
|
70
52
|
|
71
|
-
#
|
72
|
-
def on_start(&
|
73
|
-
@
|
53
|
+
# Pass in a callback that is called when start is called
|
54
|
+
def on_start(&callback)
|
55
|
+
@events.start[0] = callback
|
74
56
|
end
|
75
57
|
|
76
58
|
# pass in a callback that is called when stop is called
|
77
|
-
def on_stop(&
|
78
|
-
@
|
79
|
-
end
|
80
|
-
|
81
|
-
#
|
82
|
-
def stop_when(&
|
83
|
-
|
84
|
-
if yield
|
85
|
-
stop
|
86
|
-
true
|
87
|
-
else
|
88
|
-
false
|
89
|
-
end
|
90
|
-
end
|
91
|
-
@actions[:stop_when] = proc
|
59
|
+
def on_stop(&callback)
|
60
|
+
@events.stop[0] = callback
|
61
|
+
end
|
62
|
+
|
63
|
+
# Pass in a callback which will stop the clock if it evaluates to true
|
64
|
+
def stop_when(&callback)
|
65
|
+
@events.stop_when = callback
|
92
66
|
end
|
93
67
|
|
94
|
-
#
|
95
|
-
def on_tick(&
|
96
|
-
|
68
|
+
# Pass in a callback which will be fired on each tick
|
69
|
+
def on_tick(&callback)
|
70
|
+
wrapper = Proc.new do
|
97
71
|
unless paused?
|
98
|
-
@destinations.each
|
72
|
+
@destinations.each do |destination|
|
73
|
+
destination.send(:tick) if destination.respond_to?(:tick)
|
74
|
+
end
|
99
75
|
yield
|
100
76
|
end
|
101
77
|
end
|
102
|
-
@
|
78
|
+
@events.tick[0] = callback
|
103
79
|
end
|
104
80
|
|
105
81
|
# this will start the generator
|
@@ -109,39 +85,38 @@ module Topaz
|
|
109
85
|
#
|
110
86
|
def start(options = {})
|
111
87
|
@start_time = Time.now
|
112
|
-
@
|
88
|
+
@events.do_start
|
113
89
|
@source.start(options)
|
114
|
-
@actions[:start].call unless @actions[:start].nil?
|
115
90
|
end
|
116
91
|
|
117
|
-
#
|
92
|
+
# This will stop the clock
|
118
93
|
def stop(options = {})
|
119
|
-
@destinations.each { |dest| dest.stop }
|
120
94
|
@source.stop(options)
|
121
|
-
@
|
95
|
+
@events.do_stop
|
122
96
|
@start_time = nil
|
123
97
|
end
|
124
98
|
|
125
|
-
#
|
99
|
+
# Seconds since start was called
|
126
100
|
def time
|
127
|
-
|
101
|
+
(Time.now - @start_time).to_f unless @start_time.nil?
|
128
102
|
end
|
129
103
|
alias_method :time_since_start, :time
|
130
104
|
|
131
|
-
#
|
132
|
-
#
|
133
|
-
def add_destination(
|
134
|
-
|
135
|
-
|
136
|
-
|
105
|
+
# Add a destination
|
106
|
+
# @param [Array<UniMIDI::Output>, UniMIDI::Output] destinations
|
107
|
+
def add_destination(destinations)
|
108
|
+
destinations = [destinations].flatten.compact
|
109
|
+
destinations.each do |destination|
|
110
|
+
output = MIDISyncOutput.new(destination)
|
111
|
+
@destinations << output
|
137
112
|
end
|
138
113
|
end
|
139
114
|
|
140
|
-
#
|
141
|
-
#
|
142
|
-
def remove_destination(
|
143
|
-
|
144
|
-
|
115
|
+
# Remove a destination
|
116
|
+
# @param [Array<UniMIDI::Output>, UniMIDI::Output] destinations
|
117
|
+
def remove_destination(destinations)
|
118
|
+
destinations = [destinations].flatten.compact
|
119
|
+
destinations.each do |output|
|
145
120
|
@destinations.each do |sync_output|
|
146
121
|
@destinations.delete(sync_output) if sync_output.output == output
|
147
122
|
end
|
@@ -151,22 +126,44 @@ module Topaz
|
|
151
126
|
protected
|
152
127
|
|
153
128
|
def tick
|
154
|
-
@
|
129
|
+
@events.do_tick
|
155
130
|
end
|
156
131
|
|
157
132
|
private
|
158
133
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
134
|
+
def initialize_destinations(midi_outputs)
|
135
|
+
midi_outputs = [midi_outputs].flatten.compact
|
136
|
+
midi_outputs.each do |output|
|
137
|
+
add_destination(output)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def initialize_midi_clock_output
|
142
|
+
[:clock, :start, :stop].each do |event|
|
143
|
+
action = Proc.new do
|
144
|
+
@destinations.each { |destination| destination.send(event) }
|
166
145
|
end
|
146
|
+
@events.send("midi_#{event.to_s}=", action)
|
167
147
|
end
|
168
|
-
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def initialize_tempo_source(tempo_or_input)
|
151
|
+
@source = case tempo_or_input
|
152
|
+
when Numeric then InternalTempo.new(@events, tempo_or_input)
|
153
|
+
when UniMIDI::Input then ExternalMIDITempo.new(@events, tempo_or_input)
|
154
|
+
else
|
155
|
+
raise "You must specify an internal tempo rate or an external tempo source"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def configure(tempo_or_input, options = {}, &tick_event)
|
160
|
+
initialize_tempo_source(tempo_or_input)
|
161
|
+
initialize_destinations(options[:midi]) unless options[:midi].nil?
|
162
|
+
initialize_midi_clock_output
|
163
|
+
@source.interval = options[:interval] unless options[:interval].nil?
|
164
|
+
on_tick(&tick_event)
|
165
|
+
end
|
169
166
|
|
170
167
|
end
|
171
168
|
|
172
|
-
end
|
169
|
+
end
|
@@ -1,21 +1,22 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
module Topaz
|
3
2
|
|
3
|
+
# Calculate tempo given timestamps
|
4
4
|
class TempoCalculator
|
5
|
+
|
6
|
+
THRESHOLD = 6 # minimum number of ticks to analyze
|
5
7
|
|
6
8
|
attr_reader :tempo, :timestamps
|
7
9
|
|
8
|
-
def initialize
|
9
|
-
@tempo =
|
10
|
+
def initialize
|
11
|
+
@tempo = nil
|
10
12
|
@timestamps = []
|
11
|
-
@counter = 0
|
12
13
|
end
|
13
14
|
|
14
|
-
#
|
15
|
+
# Analyze the tempo based on the threshold
|
15
16
|
def find_tempo
|
16
17
|
tempo = nil
|
17
18
|
diffs = []
|
18
|
-
@timestamps.shift while @timestamps.length >
|
19
|
+
@timestamps.shift while @timestamps.length > THRESHOLD
|
19
20
|
@timestamps.each_with_index { |n, i| (diffs << (@timestamps[i+1] - n)) unless @timestamps[i+1].nil? }
|
20
21
|
unless diffs.empty?
|
21
22
|
avg = (diffs.inject { |a, b| a + b }.to_f / diffs.length.to_f)
|
@@ -35,4 +36,4 @@ module Topaz
|
|
35
36
|
|
36
37
|
end
|
37
38
|
|
38
|
-
end
|
39
|
+
end
|
data/lib/topaz.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
#
|
3
2
|
# MIDI syncable tempo module in Ruby
|
4
|
-
# (c)2011 Ari Russo and licensed under the Apache 2.0 License
|
3
|
+
# (c)2011-2014 Ari Russo and licensed under the Apache 2.0 License
|
5
4
|
#
|
6
5
|
|
7
|
-
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
6
|
+
# libs
|
7
|
+
require "forwardable"
|
8
|
+
require "gamelan"
|
9
|
+
require "midi-eye"
|
10
|
+
require "midi-message"
|
11
11
|
|
12
|
-
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
12
|
+
# classes
|
13
|
+
require "topaz/events"
|
14
|
+
require "topaz/external_midi_tempo"
|
15
|
+
require "topaz/internal_tempo"
|
16
|
+
require "topaz/midi_sync_output"
|
17
|
+
require "topaz/tempo_calculator"
|
18
|
+
require "topaz/tempo"
|
18
19
|
|
19
20
|
module Topaz
|
20
21
|
|
21
|
-
VERSION = "0.
|
22
|
+
VERSION = "0.1.1"
|
22
23
|
|
23
|
-
end
|
24
|
+
end
|
data/test/config.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
module TestHelper::Config
|
4
2
|
|
5
3
|
include UniMIDI
|
6
4
|
|
7
|
-
#
|
5
|
+
# Adjust these constants to suit your hardware configuration
|
8
6
|
# before running tests
|
9
7
|
|
10
|
-
TestInput = Input.
|
11
|
-
TestOutput = Output.
|
8
|
+
TestInput = Input.gets # this is the device you wish to use to test input
|
9
|
+
TestOutput = Output.gets # likewise for output
|
12
10
|
|
13
|
-
end
|
11
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
dir = File.dirname(File.expand_path(__FILE__))
|
4
|
-
$LOAD_PATH.unshift dir +
|
2
|
+
$LOAD_PATH.unshift dir + "/../lib"
|
5
3
|
|
6
|
-
require
|
7
|
-
require
|
4
|
+
require "test/unit"
|
5
|
+
require "topaz"
|
8
6
|
|
9
|
-
module TestHelper
|
10
|
-
|
11
|
-
|
7
|
+
module TestHelper
|
12
8
|
end
|
13
9
|
|
14
|
-
require File.dirname(__FILE__) +
|
10
|
+
require File.dirname(__FILE__) + "/config"
|
metadata
CHANGED
@@ -1,111 +1,130 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: midi-topaz
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.0.15
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
6
5
|
platform: ruby
|
7
|
-
authors:
|
6
|
+
authors:
|
8
7
|
- Ari Russo
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2014-08-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
17
14
|
name: gamelan
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
18
21
|
prerelease: false
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: midi-message
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.0'
|
22
34
|
- - ">="
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version:
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.0.4
|
25
37
|
type: :runtime
|
26
|
-
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: midi-message
|
29
38
|
prerelease: false
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.0'
|
33
44
|
- - ">="
|
34
|
-
- !ruby/object:Gem::Version
|
45
|
+
- !ruby/object:Gem::Version
|
35
46
|
version: 0.0.4
|
36
|
-
|
37
|
-
version_requirements: *id002
|
38
|
-
- !ruby/object:Gem::Dependency
|
47
|
+
- !ruby/object:Gem::Dependency
|
39
48
|
name: midi-eye
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.0'
|
44
54
|
- - ">="
|
45
|
-
- !ruby/object:Gem::Version
|
55
|
+
- !ruby/object:Gem::Version
|
46
56
|
version: 0.0.7
|
47
57
|
type: :runtime
|
48
|
-
version_requirements: *id003
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: unimidi
|
51
58
|
prerelease: false
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.0'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.0.7
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: unimidi
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0.1'
|
55
74
|
- - ">="
|
56
|
-
- !ruby/object:Gem::Version
|
75
|
+
- !ruby/object:Gem::Version
|
57
76
|
version: 0.1.10
|
58
77
|
type: :runtime
|
59
|
-
|
60
|
-
|
61
|
-
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.1'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 0.1.10
|
87
|
+
description: A flexible tempo source that is capable of synchronizing with MIDI clocks.
|
88
|
+
email:
|
62
89
|
- ari.russo@gmail.com
|
63
90
|
executables: []
|
64
|
-
|
65
91
|
extensions: []
|
66
|
-
|
67
92
|
extra_rdoc_files: []
|
68
|
-
|
69
|
-
|
93
|
+
files:
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
70
96
|
- lib/topaz.rb
|
71
|
-
- lib/topaz/
|
72
|
-
- lib/topaz/internal_tempo.rb
|
73
|
-
- lib/topaz/tempo_calculator.rb
|
74
|
-
- lib/topaz/tempo_source.rb
|
97
|
+
- lib/topaz/events.rb
|
75
98
|
- lib/topaz/external_midi_tempo.rb
|
99
|
+
- lib/topaz/internal_tempo.rb
|
100
|
+
- lib/topaz/midi_sync_output.rb
|
76
101
|
- lib/topaz/tempo.rb
|
77
|
-
-
|
102
|
+
- lib/topaz/tempo_calculator.rb
|
78
103
|
- test/config.rb
|
79
|
-
- test/
|
80
|
-
-
|
81
|
-
- README.rdoc
|
82
|
-
has_rdoc: true
|
104
|
+
- test/helper.rb
|
105
|
+
- test/internal_tempo_test.rb
|
83
106
|
homepage: http://github.com/arirusso/topaz
|
84
|
-
licenses:
|
85
|
-
|
107
|
+
licenses:
|
108
|
+
- Apache-2.0
|
109
|
+
metadata: {}
|
86
110
|
post_install_message:
|
87
111
|
rdoc_options: []
|
88
|
-
|
89
|
-
require_paths:
|
112
|
+
require_paths:
|
90
113
|
- lib
|
91
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
-
|
93
|
-
requirements:
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
94
116
|
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version:
|
97
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
-
|
99
|
-
requirements:
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
100
121
|
- - ">="
|
101
|
-
- !ruby/object:Gem::Version
|
122
|
+
- !ruby/object:Gem::Version
|
102
123
|
version: 1.3.6
|
103
124
|
requirements: []
|
104
|
-
|
105
125
|
rubyforge_project: midi-topaz
|
106
|
-
rubygems_version:
|
126
|
+
rubygems_version: 2.2.2
|
107
127
|
signing_key:
|
108
|
-
specification_version:
|
128
|
+
specification_version: 4
|
109
129
|
summary: MIDI syncable tempo in Ruby
|
110
130
|
test_files: []
|
111
|
-
|
data/lib/topaz/tempo_source.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
module Topaz
|
3
|
-
|
4
|
-
module TempoSource
|
5
|
-
|
6
|
-
def do_midi_clock
|
7
|
-
@actions[:midi_clock].call
|
8
|
-
end
|
9
|
-
|
10
|
-
def do_tick
|
11
|
-
@actions[:tick].call
|
12
|
-
end
|
13
|
-
|
14
|
-
def stop?
|
15
|
-
!@actions[:stop_when].nil? && @actions[:stop_when].call
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|