midi-topaz 0.1.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/topaz/api.rb +19 -0
- data/lib/topaz/clock.rb +187 -0
- data/lib/topaz/midi_clock_input.rb +186 -0
- data/lib/topaz/midi_clock_output.rb +49 -0
- data/lib/topaz/pausable.rb +32 -0
- data/lib/topaz/tempo_calculator.rb +32 -15
- data/lib/topaz/tempo_source.rb +27 -0
- data/lib/topaz/timer.rb +132 -0
- data/lib/topaz.rb +10 -6
- data/test/clock_test.rb +63 -0
- data/test/helper.rb +12 -2
- data/test/midi_clock_input_test.rb +51 -0
- data/test/midi_clock_output_test.rb +50 -0
- data/test/tempo_calculator_test.rb +39 -0
- data/test/tempo_source_test.rb +33 -0
- data/test/timer_test.rb +76 -0
- metadata +15 -9
- data/lib/topaz/events.rb +0 -44
- data/lib/topaz/external_midi_tempo.rb +0 -81
- data/lib/topaz/internal_tempo.rb +0 -88
- data/lib/topaz/midi_sync_output.rb +0 -33
- data/lib/topaz/tempo.rb +0 -164
- data/test/config.rb +0 -11
- data/test/internal_tempo_test.rb +0 -51
data/lib/topaz/internal_tempo.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
module Topaz
|
2
|
-
|
3
|
-
class InternalTempo < Gamelan::Timer
|
4
|
-
|
5
|
-
def initialize(events, tempo, options = {})
|
6
|
-
@events = events
|
7
|
-
@last = 0
|
8
|
-
@last_sync = 0
|
9
|
-
self.interval = options[:interval] || 4
|
10
|
-
|
11
|
-
super({:tempo => tempo})
|
12
|
-
end
|
13
|
-
|
14
|
-
# start the internal timer
|
15
|
-
# pass :background => true to keep the timer in a background thread
|
16
|
-
def start(options = {})
|
17
|
-
run
|
18
|
-
join unless !!options[:background]
|
19
|
-
self
|
20
|
-
end
|
21
|
-
|
22
|
-
# change the timer's click interval
|
23
|
-
def interval=(val)
|
24
|
-
@interval = val / 4
|
25
|
-
end
|
26
|
-
|
27
|
-
def interval
|
28
|
-
@interval * 4
|
29
|
-
end
|
30
|
-
|
31
|
-
# stop the timer
|
32
|
-
def stop(*a)
|
33
|
-
super()
|
34
|
-
self
|
35
|
-
end
|
36
|
-
|
37
|
-
def join
|
38
|
-
super()
|
39
|
-
self
|
40
|
-
end
|
41
|
-
|
42
|
-
protected
|
43
|
-
|
44
|
-
# Initialize the scheduler's clock, and begin executing tasks.
|
45
|
-
def run
|
46
|
-
return if @running
|
47
|
-
@running = true
|
48
|
-
@thread = Thread.new do
|
49
|
-
begin
|
50
|
-
@phase = 0.0
|
51
|
-
@origin = @time = Time.now.to_f
|
52
|
-
loop { dispatch; advance }
|
53
|
-
rescue Exception => exception
|
54
|
-
Thread.main.raise(exception)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
@thread.abort_on_exception = true
|
58
|
-
end
|
59
|
-
|
60
|
-
# Run all ready tasks.
|
61
|
-
def dispatch
|
62
|
-
# stuff to do on every tick
|
63
|
-
if time_for_midi_clock?
|
64
|
-
# look for stop
|
65
|
-
(stop and return) if @events.stop?
|
66
|
-
@events.do_midi_clock
|
67
|
-
@last_sync = (@phase * 24).to_i
|
68
|
-
end
|
69
|
-
# stuff to do on @interval
|
70
|
-
if time_for_tick?
|
71
|
-
@events.do_tick
|
72
|
-
@last = (@phase * @interval).to_i
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def time_for_midi_clock?
|
79
|
-
!@last_sync.eql?((@phase * 24).to_i)
|
80
|
-
end
|
81
|
-
|
82
|
-
def time_for_tick?
|
83
|
-
!@last.eql?((@phase * @interval).to_i)
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Topaz
|
2
|
-
|
3
|
-
# Send clock messages via MIDI
|
4
|
-
class MIDISyncOutput
|
5
|
-
|
6
|
-
attr_reader :output
|
7
|
-
|
8
|
-
def initialize(output)
|
9
|
-
@output = output
|
10
|
-
end
|
11
|
-
|
12
|
-
# Send a start message
|
13
|
-
def start(*a)
|
14
|
-
start = MIDIMessage::SystemRealtime["Start"].new.to_a
|
15
|
-
@output.puts(start)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Send a stop message
|
19
|
-
def stop(*a)
|
20
|
-
p "hi"
|
21
|
-
stop = MIDIMessage::SystemRealtime["Stop"].new.to_a
|
22
|
-
@output.puts(stop)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Send a clock tick message
|
26
|
-
def clock(*a)
|
27
|
-
clock = MIDIMessage::SystemRealtime["Clock"].new.to_a
|
28
|
-
@output.puts(clock)
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
data/lib/topaz/tempo.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
module Topaz
|
2
|
-
|
3
|
-
# Main tempo class
|
4
|
-
class Tempo
|
5
|
-
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
attr_reader :source, :destinations
|
9
|
-
|
10
|
-
def_delegators :source, :tempo, :interval, :interval=, :join
|
11
|
-
|
12
|
-
def initialize(tempo_or_input, options = {}, &tick_event)
|
13
|
-
@paused = false
|
14
|
-
@destinations = []
|
15
|
-
@events = Events.new
|
16
|
-
|
17
|
-
configure(tempo_or_input, options, &tick_event)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Change the tempo
|
21
|
-
#
|
22
|
-
# Caution that in the case that external MIDI tempo is being used, this will switch to internal
|
23
|
-
# tempo at the desired rate.
|
24
|
-
#
|
25
|
-
def tempo=(val)
|
26
|
-
if @source.respond_to?(:tempo=)
|
27
|
-
@source.tempo = val
|
28
|
-
else
|
29
|
-
@source = InternalTempo.new(@events, val)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Pause the clock
|
34
|
-
def pause
|
35
|
-
@pause = true
|
36
|
-
end
|
37
|
-
|
38
|
-
# Unpause the clock
|
39
|
-
def unpause
|
40
|
-
@pause = false
|
41
|
-
end
|
42
|
-
|
43
|
-
# Is this clock paused?
|
44
|
-
def paused?
|
45
|
-
@pause
|
46
|
-
end
|
47
|
-
|
48
|
-
# Toggle pausing the clock
|
49
|
-
def toggle_pause
|
50
|
-
paused? ? unpause : pause
|
51
|
-
end
|
52
|
-
|
53
|
-
# Pass in a callback that is called when start is called
|
54
|
-
def on_start(&callback)
|
55
|
-
@events.start[0] = callback
|
56
|
-
end
|
57
|
-
|
58
|
-
# pass in a callback that is called when stop is called
|
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
|
66
|
-
end
|
67
|
-
|
68
|
-
# Pass in a callback which will be fired on each tick
|
69
|
-
def on_tick(&callback)
|
70
|
-
wrapper = proc do
|
71
|
-
unless paused?
|
72
|
-
yield
|
73
|
-
end
|
74
|
-
end
|
75
|
-
@events.tick[0] = wrapper
|
76
|
-
end
|
77
|
-
|
78
|
-
# this will start the generator
|
79
|
-
#
|
80
|
-
# in the case that external midi tempo is being used, this will wait for a start
|
81
|
-
# or clock message
|
82
|
-
#
|
83
|
-
def start(options = {})
|
84
|
-
begin
|
85
|
-
@start_time = Time.now
|
86
|
-
@events.do_start
|
87
|
-
@source.start(options)
|
88
|
-
rescue SystemExit, Interrupt => exception
|
89
|
-
stop
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# This will stop the clock
|
94
|
-
def stop(options = {})
|
95
|
-
@source.stop(options)
|
96
|
-
@events.do_stop
|
97
|
-
@start_time = nil
|
98
|
-
end
|
99
|
-
|
100
|
-
# Seconds since start was called
|
101
|
-
def time
|
102
|
-
(Time.now - @start_time).to_f unless @start_time.nil?
|
103
|
-
end
|
104
|
-
alias_method :time_since_start, :time
|
105
|
-
|
106
|
-
# Add a destination
|
107
|
-
# @param [Array<UniMIDI::Output>, UniMIDI::Output] destinations
|
108
|
-
def add_destination(destinations)
|
109
|
-
destinations = [destinations].flatten.compact
|
110
|
-
destinations.each do |destination|
|
111
|
-
output = MIDISyncOutput.new(destination)
|
112
|
-
@destinations << output
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# Remove a destination
|
117
|
-
# @param [Array<UniMIDI::Output>, UniMIDI::Output] destinations
|
118
|
-
def remove_destination(destinations)
|
119
|
-
destinations = [destinations].flatten.compact
|
120
|
-
destinations.each do |output|
|
121
|
-
@destinations.each do |sync_output|
|
122
|
-
@destinations.delete(sync_output) if sync_output.output == output
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def initialize_destinations(midi_outputs)
|
130
|
-
midi_outputs = [midi_outputs].flatten.compact
|
131
|
-
midi_outputs.each do |output|
|
132
|
-
add_destination(output)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def initialize_midi_clock_output
|
137
|
-
[:clock, :start, :stop].each do |event|
|
138
|
-
action = proc do
|
139
|
-
@destinations.each { |destination| destination.send(event) }
|
140
|
-
end
|
141
|
-
@events.send("midi_#{event.to_s}=", action)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def initialize_tempo_source(tempo_or_input)
|
146
|
-
@source = case tempo_or_input
|
147
|
-
when Numeric then InternalTempo.new(@events, tempo_or_input)
|
148
|
-
when UniMIDI::Input then ExternalMIDITempo.new(@events, tempo_or_input)
|
149
|
-
else
|
150
|
-
raise "You must specify an internal tempo rate or an external tempo source"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def configure(tempo_or_input, options = {}, &tick_event)
|
155
|
-
initialize_tempo_source(tempo_or_input)
|
156
|
-
initialize_destinations(options[:midi]) unless options[:midi].nil?
|
157
|
-
initialize_midi_clock_output
|
158
|
-
@source.interval = options[:interval] unless options[:interval].nil?
|
159
|
-
on_tick(&tick_event)
|
160
|
-
end
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
end
|
data/test/config.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
module TestHelper::Config
|
2
|
-
|
3
|
-
include UniMIDI
|
4
|
-
|
5
|
-
# Adjust these constants to suit your hardware configuration
|
6
|
-
# before running tests
|
7
|
-
|
8
|
-
TestInput = Input.gets # this is the device you wish to use to test input
|
9
|
-
TestOutput = Output.gets # likewise for output
|
10
|
-
|
11
|
-
end
|
data/test/internal_tempo_test.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require "helper"
|
2
|
-
|
3
|
-
class InternalTempoTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
include Topaz
|
6
|
-
include TestHelper
|
7
|
-
|
8
|
-
def test_stop_when
|
9
|
-
i = 0
|
10
|
-
count_to = 5
|
11
|
-
|
12
|
-
tempo = Tempo.new(120) { i += 1 }
|
13
|
-
tempo.stop_when { i.eql?(count_to) }
|
14
|
-
tempo.start
|
15
|
-
|
16
|
-
assert_equal(count_to, i)
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_change_on_tick
|
20
|
-
i = 0
|
21
|
-
count_to = 5
|
22
|
-
|
23
|
-
tempo = Tempo.new(120) { i += 1 }
|
24
|
-
tempo.stop_when { i == count_to }
|
25
|
-
tempo.start
|
26
|
-
|
27
|
-
assert_equal(count_to, i)
|
28
|
-
|
29
|
-
i = 0
|
30
|
-
count_to = 1000
|
31
|
-
|
32
|
-
tempo.on_tick { i += 100 }
|
33
|
-
tempo.stop_when { i == count_to }
|
34
|
-
tempo.start
|
35
|
-
|
36
|
-
assert_equal(count_to, i)
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_internal_interval
|
42
|
-
tempo = Tempo.new(120)
|
43
|
-
|
44
|
-
assert_equal(4, tempo.interval)
|
45
|
-
|
46
|
-
tempo.interval = 8
|
47
|
-
|
48
|
-
assert_equal(8, tempo.interval)
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|