midi-topaz 0.1.2 → 0.2.1
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.
- 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
|