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.
@@ -0,0 +1,132 @@
1
+ module Topaz
2
+
3
+ class Timer < Gamelan::Timer
4
+
5
+ include Pausable
6
+
7
+ attr_reader :phase, :running
8
+ alias_method :running?, :running
9
+
10
+ # @param [Fixnum] tempo
11
+ # @param [Hash] options
12
+ # @option options [Clock::Event] :event
13
+ def initialize(tempo, options = {})
14
+ @event = options[:event]
15
+ @last_tick_event = 0
16
+ @last_midi_clock = 0
17
+ @pause = false
18
+ @running = false
19
+ self.interval = options[:interval] || 4
20
+
21
+ super({ :tempo => tempo })
22
+ end
23
+
24
+ # Start the internal timer
25
+ # @param [Hash] options
26
+ # @option options [Boolean] :background Whether to run the timer in a background thread (default: false)
27
+ # @return [Timer]
28
+ def start(options = {})
29
+ run
30
+ !@event.nil? && @event.do_start
31
+ join unless !!options[:background]
32
+ self
33
+ end
34
+
35
+ # Set the timer's click interval
36
+ # @param [Fixnum] value
37
+ # @return [Fixnum]
38
+ def interval=(value)
39
+ @interval = value / 4
40
+ end
41
+
42
+ # The timer's click interval
43
+ # @return [Fixnum]
44
+ def interval
45
+ @interval * 4
46
+ end
47
+
48
+ # Stop the timer
49
+ # @return [Timer]
50
+ def stop(*a)
51
+ super()
52
+ !@event.nil? && @event.do_stop
53
+ self
54
+ end
55
+
56
+ # Join the timer thread
57
+ # @return [Timer]
58
+ def join
59
+ super()
60
+ self
61
+ end
62
+
63
+ protected
64
+
65
+ # Initialize the scheduler's clock, and begin executing tasks
66
+ # @return [Boolean]
67
+ def run
68
+ unless @running
69
+ @thread = Thread.new do
70
+ begin
71
+ initialize_running_state
72
+ loop do
73
+ dispatch
74
+ advance
75
+ end
76
+ rescue Exception => exception
77
+ Thread.main.raise(exception)
78
+ end
79
+ end
80
+ @thread.abort_on_exception = true
81
+ true
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ # Run all ready tasks.
88
+ # @return [Boolean]
89
+ def dispatch
90
+ # Stuff to do on every tick
91
+ if time_for_midi_clock?
92
+ # look for stop
93
+ !@event.nil? && @event.do_clock
94
+ @last_midi_clock = (@phase * 24).to_i
95
+ true
96
+ end
97
+ # Stuff to do on @interval
98
+ if time_for_tick?
99
+ !@event.nil? && !@pause && @event.do_tick
100
+ @last_tick_event = (@phase * @interval).to_i
101
+ true
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Initialize the variables that handle the running process of the timer
108
+ # @return [Boolean]
109
+ def initialize_running_state
110
+ @running = true
111
+ @phase = 0.0
112
+ @origin = @time = Time.now.to_f
113
+ true
114
+ end
115
+
116
+ # Is the current phase appropriate for MIDI clock output?
117
+ # @return [Boolean]
118
+ def time_for_midi_clock?
119
+ phase = (@phase * 24).to_i
120
+ !@last_midi_clock.eql?(phase)
121
+ end
122
+
123
+ # Is the current phase appropriate for the tick event?
124
+ # @return [Boolean]
125
+ def time_for_tick?
126
+ phase = (@phase * @interval).to_i
127
+ !@last_tick_event.eql?(phase)
128
+ end
129
+
130
+ end
131
+
132
+ end
data/lib/topaz.rb CHANGED
@@ -9,16 +9,20 @@ require "gamelan"
9
9
  require "midi-eye"
10
10
  require "midi-message"
11
11
 
12
+ # modules
13
+ require "topaz/api"
14
+ require "topaz/pausable"
15
+ require "topaz/tempo_source"
16
+
12
17
  # classes
13
- require "topaz/events"
14
- require "topaz/external_midi_tempo"
15
- require "topaz/internal_tempo"
16
- require "topaz/midi_sync_output"
18
+ require "topaz/clock"
19
+ require "topaz/midi_clock_input"
20
+ require "topaz/midi_clock_output"
17
21
  require "topaz/tempo_calculator"
18
- require "topaz/tempo"
22
+ require "topaz/timer"
19
23
 
20
24
  module Topaz
21
25
 
22
- VERSION = "0.1.2"
26
+ VERSION = "0.2.1"
23
27
 
24
28
  end
@@ -0,0 +1,63 @@
1
+ require "helper"
2
+
3
+ class Topaz::ClockTest < Test::Unit::TestCase
4
+
5
+ context "Clock" do
6
+
7
+ setup do
8
+ @counter = 0
9
+ @count_to = 5
10
+ @tempo = Topaz::Clock.new(120) { @counter += 1 }
11
+ end
12
+
13
+ context "#interval=" do
14
+
15
+ should "change interval" do
16
+ assert_equal(4, @tempo.interval)
17
+ @tempo.interval = 8
18
+ assert_equal(8, @tempo.interval)
19
+ end
20
+
21
+ end
22
+
23
+ context "Event" do
24
+
25
+ context "#tick" do
26
+
27
+ should "change tick" do
28
+ @is_running = false
29
+ @tempo.event.tick { @is_running = true }
30
+ refute @is_running
31
+
32
+ @tempo.start(:background => true)
33
+ loop until @tempo.running?
34
+ loop until @is_running
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ context "Trigger" do
43
+
44
+ context "#stop" do
45
+
46
+ setup do
47
+ @tempo.trigger.stop { @counter.eql?(@count_to) }
48
+ end
49
+
50
+ should "stop" do
51
+ @tempo.start
52
+ assert_equal(@count_to, @counter)
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+
60
+
61
+ end
62
+ end
63
+
data/test/helper.rb CHANGED
@@ -2,9 +2,19 @@ dir = File.dirname(File.expand_path(__FILE__))
2
2
  $LOAD_PATH.unshift dir + "/../lib"
3
3
 
4
4
  require "test/unit"
5
+ require "mocha/test_unit"
6
+ require "shoulda-context"
5
7
  require "topaz"
6
8
 
7
- module TestHelper
9
+ module TestHelper
10
+
11
+ def self.select_devices
12
+ $test_device ||= {}
13
+ { :input => UniMIDI::Input, :output => UniMIDI::Output }.each do |type, klass|
14
+ $test_device[type] = klass.gets
15
+ end
16
+ end
17
+
8
18
  end
9
19
 
10
- require File.dirname(__FILE__) + "/config"
20
+ TestHelper.select_devices
@@ -0,0 +1,51 @@
1
+ require "helper"
2
+
3
+ class Topaz::MIDIClockInputTest < Test::Unit::TestCase
4
+
5
+ context "MIDIClockInput" do
6
+
7
+ setup do
8
+ @input = Object.new
9
+ @event = Object.new
10
+ @clock = Topaz::MIDIClockInput.new(@input, :event => @event)
11
+ end
12
+
13
+ context "#thru" do
14
+
15
+ should "fire clock event" do
16
+ @event.expects(:do_clock).once
17
+ assert @clock.send(:thru)
18
+ end
19
+
20
+ end
21
+
22
+ context "#tick" do
23
+
24
+ should "fire tick event" do
25
+ @event.expects(:do_tick).once
26
+ assert @clock.send(:tick)
27
+ end
28
+
29
+ end
30
+
31
+ context "#tick?" do
32
+
33
+ should "tick when counter has reached tick threshold" do
34
+ assert_equal 4, @clock.interval
35
+ 23.times do
36
+ refute @clock.send(:tick?)
37
+ @clock.send(:advance)
38
+ end
39
+ assert @clock.send(:tick?)
40
+ @clock.send(:advance)
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+
50
+
51
+
@@ -0,0 +1,50 @@
1
+ require "helper"
2
+
3
+ class Topaz::MIDIClockOutputTest < Test::Unit::TestCase
4
+
5
+ context "MIDIClockOutput" do
6
+
7
+ setup do
8
+ @output = Object.new
9
+ @clock = Topaz::MIDIClockOutput.new(:device => @output)
10
+ end
11
+
12
+ context "#do_start" do
13
+
14
+ should "emit start message" do
15
+ @output.expects(:puts).once.with(250)
16
+ result = @clock.do_start
17
+ assert result
18
+ @output.unstub(:puts)
19
+ end
20
+
21
+ end
22
+
23
+ context "#do_stop" do
24
+
25
+ should "emit stop message" do
26
+ @output.expects(:puts).once.with(252)
27
+ result = @clock.do_stop
28
+ assert result
29
+ @output.unstub(:puts)
30
+ end
31
+
32
+ end
33
+
34
+ context "#do_clock" do
35
+
36
+ should "emit clock message" do
37
+ @output.expects(:puts).once.with(248)
38
+ result = @clock.do_clock
39
+ assert result
40
+ @output.unstub(:puts)
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+
50
+
@@ -0,0 +1,39 @@
1
+ require "helper"
2
+
3
+ class Topaz::TempoCalculatorTest < Test::Unit::TestCase
4
+
5
+ context "TempoCalculator" do
6
+
7
+ setup do
8
+ @calc = Topaz::TempoCalculator.new
9
+ end
10
+
11
+ context "#calculate" do
12
+
13
+ should "wait for two timestamps to start calculation" do
14
+ @calc.timestamps << 1
15
+ assert_nil @calc.calculate
16
+ @calc.timestamps << 2
17
+ @calc.timestamps << 3
18
+ assert_not_nil @calc.calculate
19
+ end
20
+
21
+ should "limit timestamps do within the threshold" do
22
+ 10.times { |i| @calc.timestamps << i }
23
+ @calc.calculate
24
+ assert_equal Topaz::TempoCalculator::THRESHOLD, @calc.timestamps.count
25
+ end
26
+
27
+ should "express tempo" do
28
+ 5.times { |i| @calc.timestamps << Time.now.to_f; sleep(1.0 / 24.0) }
29
+ result = @calc.calculate
30
+ assert_not_nil result
31
+ assert (58..62).include?(result)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,33 @@
1
+ require "helper"
2
+
3
+ class Topaz::TempoSourceTest < Test::Unit::TestCase
4
+
5
+ context "TempoSource" do
6
+
7
+ context ".new" do
8
+
9
+ should "construct a timer" do
10
+ result = Topaz::TempoSource.new(120)
11
+ assert_not_nil result
12
+ assert_equal Topaz::Timer, result.class
13
+ end
14
+
15
+ should "construct a midi input clock" do
16
+ result = Topaz::TempoSource.new($test_device[:input])
17
+ assert_not_nil result
18
+ assert_equal Topaz::MIDIClockInput, result.class
19
+ end
20
+
21
+ should "throw an exception" do
22
+ assert_raise RuntimeError do
23
+ Topaz::TempoSource.new("something")
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+
@@ -0,0 +1,76 @@
1
+ require "helper"
2
+
3
+ class Topaz::TimerTest < Test::Unit::TestCase
4
+
5
+ context "Timer" do
6
+
7
+ setup do
8
+ @timer = Topaz::Timer.new(120)
9
+ end
10
+
11
+ context "#dispatch" do
12
+
13
+ setup do
14
+ @event = Object.new
15
+ @timer = Topaz::Timer.new(120, :event => @event)
16
+ @timer.send(:initialize_running_state)
17
+ end
18
+
19
+ should "fire MIDI clock event" do
20
+ @event.expects(:do_clock).once.returns(true)
21
+ @timer.send(:dispatch)
22
+ end
23
+
24
+ should "fire tick event" do
25
+ @event.expects(:do_tick).once.returns(true)
26
+ @timer.send(:dispatch)
27
+ end
28
+
29
+ end
30
+
31
+ context "#start" do
32
+
33
+ should "start running" do
34
+ assert_nil @timer.phase
35
+ @timer.start(:background => true)
36
+ loop until @timer.running?
37
+
38
+ assert @timer.running?
39
+ assert_not_nil @timer.phase
40
+ assert_not_equal 0.0, @timer.phase
41
+ end
42
+
43
+ end
44
+
45
+ context "#stop" do
46
+
47
+ setup do
48
+ assert_nil @timer.phase
49
+ @timer.start(:background => true)
50
+ loop until @timer.running?
51
+
52
+ assert @timer.running?
53
+ end
54
+
55
+ should "stop running" do
56
+ assert_not_nil @timer.phase
57
+ assert_not_equal 0.0, @timer.phase
58
+ @timer.stop
59
+ refute @timer.running?
60
+ end
61
+
62
+ end
63
+
64
+ context "#interval=" do
65
+
66
+ should "change interval" do
67
+ assert_equal(4, @timer.interval)
68
+ @timer.interval = 8
69
+ assert_equal(8, @timer.interval)
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: midi-topaz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ari Russo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-19 00:00:00.000000000 Z
11
+ date: 2014-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gamelan
@@ -94,15 +94,21 @@ files:
94
94
  - LICENSE
95
95
  - README.md
96
96
  - lib/topaz.rb
97
- - lib/topaz/events.rb
98
- - lib/topaz/external_midi_tempo.rb
99
- - lib/topaz/internal_tempo.rb
100
- - lib/topaz/midi_sync_output.rb
101
- - lib/topaz/tempo.rb
97
+ - lib/topaz/api.rb
98
+ - lib/topaz/clock.rb
99
+ - lib/topaz/midi_clock_input.rb
100
+ - lib/topaz/midi_clock_output.rb
101
+ - lib/topaz/pausable.rb
102
102
  - lib/topaz/tempo_calculator.rb
103
- - test/config.rb
103
+ - lib/topaz/tempo_source.rb
104
+ - lib/topaz/timer.rb
105
+ - test/clock_test.rb
104
106
  - test/helper.rb
105
- - test/internal_tempo_test.rb
107
+ - test/midi_clock_input_test.rb
108
+ - test/midi_clock_output_test.rb
109
+ - test/tempo_calculator_test.rb
110
+ - test/tempo_source_test.rb
111
+ - test/timer_test.rb
106
112
  homepage: http://github.com/arirusso/topaz
107
113
  licenses:
108
114
  - Apache-2.0
data/lib/topaz/events.rb DELETED
@@ -1,44 +0,0 @@
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,81 +0,0 @@
1
- module Topaz
2
-
3
- # Trigger an event based on received midi clock messages
4
- class ExternalMIDITempo
5
-
6
- attr_reader :clock
7
-
8
- def initialize(events, input, options = {})
9
- @events = events
10
- @tempo_calculator = TempoCalculator.new
11
- @clock = MIDIEye::Listener.new(input)
12
- self.interval = options[:interval] || 4
13
-
14
- initialize_clock
15
- end
16
-
17
- # This will return a calculated tempo
18
- def tempo
19
- @tempo_calculator.find_tempo
20
- end
21
-
22
- def start(*a)
23
- @clock.start(*a)
24
- self
25
- end
26
-
27
- def stop(*a)
28
- @clock.stop
29
- self
30
- end
31
-
32
- def join
33
- @clock.join
34
- self
35
- end
36
-
37
- #
38
- # change the clock interval
39
- # defaults to click once every 24 ticks or one quarter note which is the MIDI standard.
40
- # however, if you wish to fire the on_tick event twice as often
41
- # (or once per 12 clicks), pass 8
42
- #
43
- # 1 = whole note
44
- # 2 = half note
45
- # 4 = quarter note
46
- # 6 = dotted quarter
47
- # 8 = eighth note
48
- # 16 = sixteenth note
49
- # etc
50
- #
51
- def interval=(val)
52
- per_qn = val / 4
53
- @per_tick = 24 / per_qn
54
- end
55
-
56
- # Return the interval at which the tick event is fired
57
- def interval
58
- 4 * (24 / @per_tick)
59
- end
60
-
61
- private
62
-
63
- def initialize_clock
64
- @counter = 0
65
- # Note that this doesn't wait for a start signal
66
- @clock.listen_for(:name => "Clock") do |msg|
67
- (stop and return) if @events.stop?
68
- @events.do_midi_clock # thru
69
- @tempo_calculator.timestamps << msg[:timestamp]
70
- if @counter.eql?(@per_tick)
71
- @events.do_tick
72
- @counter = 0
73
- else
74
- @counter += 1
75
- end
76
- end
77
- end
78
-
79
- end
80
-
81
- end