basic-sequencer 0.0.4
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 +7 -0
- data/LICENSE +13 -0
- data/README.md +25 -0
- data/lib/sequencer.rb +24 -0
- data/lib/sequencer/clock.rb +75 -0
- data/lib/sequencer/core.rb +77 -0
- data/lib/sequencer/event.rb +86 -0
- data/lib/sequencer/event_trigger.rb +78 -0
- data/lib/sequencer/loop.rb +60 -0
- data/test/clock_test.rb +51 -0
- data/test/core_test.rb +133 -0
- data/test/event_trigger_test.rb +89 -0
- data/test/helper.rb +15 -0
- data/test/loop_test.rb +89 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69fcf47b967d669ca28420c35c71dd4a642229da
|
4
|
+
data.tar.gz: 96e296ed53e0f27a539dcfb8f32a9ebf461fc519
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79b566c5927160eead43d655a1a06c08eea322f63b6ec12bb1bcbf5ed64cfd48ad75aaa9210107829ef39fac244a42f6350278cd9fe3cc96d4084de9bc03d303
|
7
|
+
data.tar.gz: dae58578dc14438b6133954e076679ad6bad8da0606ece4b222cf3488a80b7b934997ca93ffda0693425a8983f0e9286d0267ba5adb47da185e590e8c2286512
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011-2014 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.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#Sequencer
|
2
|
+
|
3
|
+
Perform a sequence of events at tempo
|
4
|
+
|
5
|
+
The tempo clock is MIDI syncable by way of [topaz](http://github.com/arirusso/topaz).
|
6
|
+
|
7
|
+
This is used by [diamond](http://github.com/arirusso/diamond)
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
`gem install basic-sequencer`
|
12
|
+
|
13
|
+
or with Bundler, add this to your Gemfile
|
14
|
+
|
15
|
+
`gem "basic-sequencer"`
|
16
|
+
|
17
|
+
## Documentation
|
18
|
+
|
19
|
+
* [rdoc](http://rubydoc.info/github/arirusso/sequencer)
|
20
|
+
|
21
|
+
## License
|
22
|
+
|
23
|
+
Licensed under Apache 2.0, See the file LICENSE
|
24
|
+
|
25
|
+
Copyright © 2011-2014 Ari Russo
|
data/lib/sequencer.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
# Sequencer
|
3
|
+
# Perform a sequence of events at tempo
|
4
|
+
#
|
5
|
+
# (c)2011-2014 Ari Russo
|
6
|
+
# Licensed under Apache 2.0
|
7
|
+
#
|
8
|
+
|
9
|
+
# libs
|
10
|
+
require "forwardable"
|
11
|
+
require "topaz"
|
12
|
+
|
13
|
+
# classes
|
14
|
+
require "sequencer/clock"
|
15
|
+
require "sequencer/core"
|
16
|
+
require "sequencer/event"
|
17
|
+
require "sequencer/event_trigger"
|
18
|
+
require "sequencer/loop"
|
19
|
+
|
20
|
+
module Sequencer
|
21
|
+
|
22
|
+
VERSION = "0.0.4"
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Sequencer
|
2
|
+
|
3
|
+
# A light wrapper for Topaz::Tempo that adds some event handling
|
4
|
+
class Clock
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :event
|
9
|
+
def_delegators :@clock, :pause, :stop, :unpause
|
10
|
+
|
11
|
+
# @param [Fixnum, UniMIDI::Input] tempo_or_input
|
12
|
+
# @param [Hash] options
|
13
|
+
# @option options [Array<UniMIDI::Output>, UniMIDI::Output] :outputs MIDI output device(s)
|
14
|
+
def initialize(tempo_or_input, options = {})
|
15
|
+
@event = Event.new
|
16
|
+
initialize_clock(tempo_or_input, options.fetch(:resolution, 128), :outputs => options[:outputs])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Start the clock
|
20
|
+
# @param [Hash] options
|
21
|
+
# @option options [Boolean] :blocking Whether to run in the foreground (also :focus, :foreground)
|
22
|
+
# @option options [Boolean] :suppress_clock Whether this clock is a sync-slave
|
23
|
+
# @return [Boolean]
|
24
|
+
def start(options = {})
|
25
|
+
clock_options = {}
|
26
|
+
clock_options[:background] = ![:blocking, :focus, :foreground].any? { |key| !!options[key] }
|
27
|
+
@clock.start(clock_options) unless !!options[:suppress_clock]
|
28
|
+
Thread.abort_on_exception = true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Action taken by the clock on a tick. Fires the tick event
|
34
|
+
def on_tick
|
35
|
+
@event.do_tick
|
36
|
+
end
|
37
|
+
|
38
|
+
# Instantiate the underlying clock object
|
39
|
+
# @param [Fixnum, UniMIDI::Input] tempo_or_input
|
40
|
+
# @param [Fixnum] resolution
|
41
|
+
# @param [Hash] options
|
42
|
+
# @option options [Array<UniMIDI::Output>, UniMIDI::Output] :outputs MIDI output device(s)
|
43
|
+
def initialize_clock(tempo_or_input, resolution, options = {})
|
44
|
+
@clock = Topaz::Tempo.new(tempo_or_input, :midi => options[:outputs])
|
45
|
+
@clock.interval = @clock.interval * (resolution / @clock.interval)
|
46
|
+
@clock.on_tick { on_tick }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Clock event callbacks
|
50
|
+
class Event
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@tick = []
|
54
|
+
end
|
55
|
+
|
56
|
+
# Access the tick event callback
|
57
|
+
# @param [Proc] block
|
58
|
+
# @return [Proc]
|
59
|
+
def tick(&block)
|
60
|
+
if block_given?
|
61
|
+
@tick.clear
|
62
|
+
@tick << block
|
63
|
+
end
|
64
|
+
@tick
|
65
|
+
end
|
66
|
+
|
67
|
+
# Fire the tick event callback
|
68
|
+
# @return [Boolean]
|
69
|
+
def do_tick
|
70
|
+
!@tick.empty? && @tick.map(&:call)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Sequencer
|
2
|
+
|
3
|
+
# The core sequencer
|
4
|
+
class Core
|
5
|
+
|
6
|
+
attr_reader :event, :loop, :trigger
|
7
|
+
attr_accessor :pointer
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@event = Event.new
|
11
|
+
@loop = Loop.new
|
12
|
+
@pointer = 0
|
13
|
+
@trigger = EventTrigger.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Execute a single cycle of sequencing (perform and step)
|
17
|
+
# @param [Array] sequence The sequence to execute a single cycle of
|
18
|
+
# @return [Boolean] Whether perform and step both finished
|
19
|
+
def exec(sequence)
|
20
|
+
perform(sequence) && step(sequence)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Step the sequencer and fire the step event
|
24
|
+
# @param [Array] sequence
|
25
|
+
# @return [Boolean]
|
26
|
+
def step(sequence)
|
27
|
+
if reset_pointer?(:length => sequence.length)
|
28
|
+
reset_pointer
|
29
|
+
else
|
30
|
+
@pointer += 1
|
31
|
+
end
|
32
|
+
@event.do_step
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# If a stop is triggered, stop. Otherwise if reset is triggered, reset. Finally,
|
37
|
+
# fire the perform event on the current step of the sequence
|
38
|
+
# @param [Array] sequence
|
39
|
+
# @return [Boolean] True if perform event was fired
|
40
|
+
def perform(sequence)
|
41
|
+
data = sequence.at(@pointer)
|
42
|
+
@event.do_next(@pointer, data) if @event.next?(@pointer)
|
43
|
+
if @trigger.stop?(@pointer, data)
|
44
|
+
@event.do_stop
|
45
|
+
false
|
46
|
+
else
|
47
|
+
reset_pointer if @trigger.reset?(@pointer, data)
|
48
|
+
@event.do_perform(data)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the pointer to the loop start point
|
54
|
+
# @return [Fixnum]
|
55
|
+
def reset_pointer
|
56
|
+
@pointer = @loop.next
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Is the pointer at the point where it needs to be reset?
|
62
|
+
# @param [Hash] options
|
63
|
+
# @option options [Fixnum] :length The length of the sequence (used when the default loop is active)
|
64
|
+
# @return [Boolean]
|
65
|
+
def reset_pointer?(options = {})
|
66
|
+
!@loop.disabled? && !@loop.in_bounds?(@pointer + 1, :length => options[:length])
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
# Shortcut to the Core constructor
|
72
|
+
# @return [Core]
|
73
|
+
def self.new
|
74
|
+
Core.new
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Sequencer
|
2
|
+
|
3
|
+
# Events that are fired by the sequencer
|
4
|
+
class Event
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@next = {}
|
8
|
+
@perform = []
|
9
|
+
@step = []
|
10
|
+
@stop = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def next(pointer = nil, &block)
|
14
|
+
if block_given?
|
15
|
+
@next[pointer] ||= []
|
16
|
+
@next[pointer] << block
|
17
|
+
end
|
18
|
+
hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def next?(pointer = nil)
|
22
|
+
!@next[pointer].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_next(pointer, data)
|
26
|
+
keys = [pointer, nil]
|
27
|
+
callbacks = keys.map { |key| @next.delete(key) }.flatten.compact
|
28
|
+
callbacks.each(&:call)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set the step event
|
33
|
+
# @param [Proc] block
|
34
|
+
# @return [Proc]
|
35
|
+
def step(&block)
|
36
|
+
if block_given?
|
37
|
+
@step.clear
|
38
|
+
@step << block
|
39
|
+
end
|
40
|
+
@step
|
41
|
+
end
|
42
|
+
|
43
|
+
# Fire the step events
|
44
|
+
# @return [Boolean]
|
45
|
+
def do_step
|
46
|
+
!@step.empty? && @step.map(:call)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Access the stop events
|
50
|
+
# @param [Proc] block
|
51
|
+
# @return [Proc]
|
52
|
+
def stop(&block)
|
53
|
+
if block_given?
|
54
|
+
@stop.clear
|
55
|
+
@stop << block
|
56
|
+
end
|
57
|
+
@stop
|
58
|
+
end
|
59
|
+
|
60
|
+
# Fire the stop event
|
61
|
+
# @return [Boolean]
|
62
|
+
def do_stop
|
63
|
+
@stop.map(&:call)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the perform event
|
67
|
+
# @param [Proc] block
|
68
|
+
# @return [Proc]
|
69
|
+
def perform(&block)
|
70
|
+
if block_given?
|
71
|
+
@perform.clear
|
72
|
+
@perform << block
|
73
|
+
end
|
74
|
+
@perform
|
75
|
+
end
|
76
|
+
|
77
|
+
# Fire the perform event
|
78
|
+
# @param [Object] data Data for the current sequence step
|
79
|
+
# @return [Boolean]
|
80
|
+
def do_perform(data)
|
81
|
+
@perform.map { |callback| callback.call(data) }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Sequencer
|
2
|
+
|
3
|
+
# Callbacks that when evaluate to true, will trigger the corresponding sequencer event
|
4
|
+
class EventTrigger
|
5
|
+
|
6
|
+
# Set the reset trigger. When true, the sequence will go back to step 0
|
7
|
+
# @param [Proc] block
|
8
|
+
# @return [Proc]
|
9
|
+
def reset(&block)
|
10
|
+
@reset = block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the rest trigger. When true, no messages will be outputted during that step
|
14
|
+
# @param [Proc] block
|
15
|
+
# @return [Proc]
|
16
|
+
def rest(&block)
|
17
|
+
@rest = block
|
18
|
+
end
|
19
|
+
|
20
|
+
# Whether the reset event should fire
|
21
|
+
# @param [Fixnum] pointer The sequencer pointer
|
22
|
+
# @param [Object] data Data for the current sequence step
|
23
|
+
# @return [Boolean]
|
24
|
+
def reset?(pointer, data)
|
25
|
+
!@reset.nil? && @reset.call(pointer, data)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Whether the rest event should fire
|
29
|
+
# @param [Fixnum] pointer The sequencer pointer
|
30
|
+
# @param [Object] data Data for the current sequence step
|
31
|
+
# @return [Boolean]
|
32
|
+
def rest?(pointer, data)
|
33
|
+
!@rest.nil? && @rest.call(pointer, data)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set the stop trigger. When true, the sequencer will stop
|
37
|
+
# @param [Proc] block
|
38
|
+
# @return [Proc]
|
39
|
+
def stop(&block)
|
40
|
+
@stop = block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Whether to fire the stop event
|
44
|
+
# @param [Fixnum] pointer The sequencer pointer
|
45
|
+
# @param [Object] data Data for the current sequence step
|
46
|
+
# @return [Boolean]
|
47
|
+
def stop?(pointer, data)
|
48
|
+
!@stop.nil? && @stop.call(pointer, data)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Shortcut to trigger a rest event on a given interval of ticks
|
52
|
+
# @param [Fixnum, nil] num The number of ticks or nil to cancel existing triggers
|
53
|
+
# @return [Fixnum, nil]
|
54
|
+
def rest_every(num)
|
55
|
+
if num.nil?
|
56
|
+
@rest = nil
|
57
|
+
else
|
58
|
+
rest { |pointer| pointer % num == 0 }
|
59
|
+
num
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Shortcut to trigger a reset even on a given interval of ticks
|
64
|
+
# @param [Fixnum, nil] num The number of ticks or nil to cancel existing triggers
|
65
|
+
# @return [Fixnum, nil]
|
66
|
+
def reset_every(num)
|
67
|
+
if num.nil?
|
68
|
+
@reset = nil
|
69
|
+
else
|
70
|
+
reset { |pointer| pointer % num == 0 }
|
71
|
+
num
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Sequencer
|
2
|
+
|
3
|
+
class Loop
|
4
|
+
|
5
|
+
attr_reader :count, :range
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@count = 0
|
9
|
+
@range = nil
|
10
|
+
@is_disabled = false
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the loop range
|
14
|
+
# @param [Array<Fixnum>, FalseClass, Fixnum, Range] value
|
15
|
+
# @return [FalseClass, Range]
|
16
|
+
def range=(value)
|
17
|
+
@range = to_range(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
default? ? 0 : @range.begin
|
22
|
+
end
|
23
|
+
|
24
|
+
def next
|
25
|
+
@count += 1
|
26
|
+
start
|
27
|
+
end
|
28
|
+
|
29
|
+
def default?
|
30
|
+
@range.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def disabled?
|
34
|
+
@is_disabled
|
35
|
+
end
|
36
|
+
|
37
|
+
def disable
|
38
|
+
@is_disabled = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def in_bounds?(num, options = {})
|
42
|
+
length = options[:length]
|
43
|
+
range = default? ? 0..(length-1) : @range
|
44
|
+
range.include?(num)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @param [Array<Fixnum>, Fixnum, Range] value
|
50
|
+
# @return [FalseClass, Range]
|
51
|
+
def to_range(value)
|
52
|
+
case value
|
53
|
+
when Array then (value[0]..value[1])
|
54
|
+
when Fixnum then (0..value)
|
55
|
+
when Range then value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/test/clock_test.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Sequencer::ClockTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Clock" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@clock = Sequencer::Clock.new(120)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Clock::Event#tick" do
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@flag = false
|
15
|
+
@flag2 = false
|
16
|
+
end
|
17
|
+
|
18
|
+
should "assign single callback" do
|
19
|
+
@clock.event.tick { @flag = true }
|
20
|
+
refute @flag
|
21
|
+
@clock.event.do_tick
|
22
|
+
assert @flag
|
23
|
+
end
|
24
|
+
|
25
|
+
should "reassign single callback" do
|
26
|
+
@clock.event.tick { @flag = true }
|
27
|
+
@clock.event.tick { @flag2 = true }
|
28
|
+
refute @flag
|
29
|
+
refute @flag2
|
30
|
+
@clock.event.do_tick
|
31
|
+
refute @flag
|
32
|
+
assert @flag2
|
33
|
+
end
|
34
|
+
|
35
|
+
should "allow multiple callbacks" do
|
36
|
+
@clock.event.tick { @flag = true }
|
37
|
+
@clock.event.tick << proc { @flag2 = true }
|
38
|
+
refute @flag
|
39
|
+
refute @flag2
|
40
|
+
@clock.event.do_tick
|
41
|
+
assert @flag
|
42
|
+
assert @flag2
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
data/test/core_test.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Sequencer::CoreTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Core" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@sequencer = Sequencer::Core.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#exec" do
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@sequence = [1,2,3,4]
|
15
|
+
end
|
16
|
+
|
17
|
+
should "move to next" do
|
18
|
+
assert_equal 0, @sequencer.pointer
|
19
|
+
@sequencer.exec(@sequence)
|
20
|
+
assert_equal 1, @sequencer.pointer
|
21
|
+
end
|
22
|
+
|
23
|
+
should "do repeat" do
|
24
|
+
@sequencer.pointer = @sequence.length - 1
|
25
|
+
@sequencer.exec(@sequence)
|
26
|
+
assert_equal 0, @sequencer.pointer
|
27
|
+
end
|
28
|
+
|
29
|
+
should "stop" do
|
30
|
+
@sequencer.trigger.expects(:stop?).once.returns(true)
|
31
|
+
@sequencer.trigger.expects(:reset?).never
|
32
|
+
@sequencer.event.expects(:do_perform).never
|
33
|
+
@sequencer.exec(@sequence)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "reset and fire event" do
|
37
|
+
@sequencer.pointer = 3
|
38
|
+
@sequencer.trigger.expects(:stop?).once.returns(false)
|
39
|
+
@sequencer.trigger.expects(:reset?).once.returns(true)
|
40
|
+
@sequencer.event.expects(:do_perform).once
|
41
|
+
@sequencer.exec(@sequence)
|
42
|
+
assert_equal 1, @sequencer.pointer
|
43
|
+
end
|
44
|
+
|
45
|
+
should "fire event" do
|
46
|
+
@sequencer.pointer = 2
|
47
|
+
@sequencer.trigger.expects(:stop?).once.returns(false)
|
48
|
+
@sequencer.trigger.expects(:reset?).once.returns(false)
|
49
|
+
@sequencer.event.expects(:do_perform).once
|
50
|
+
@sequencer.exec(@sequence)
|
51
|
+
assert_equal 3, @sequencer.pointer
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
context "#step" do
|
57
|
+
|
58
|
+
setup do
|
59
|
+
@sequence = [1,2,3,4]
|
60
|
+
@sequencer.event.expects(:do_step).once
|
61
|
+
end
|
62
|
+
|
63
|
+
should "move to next" do
|
64
|
+
assert_equal 0, @sequencer.pointer
|
65
|
+
@sequencer.step(@sequence)
|
66
|
+
assert_equal 1, @sequencer.pointer
|
67
|
+
end
|
68
|
+
|
69
|
+
should "do repeat" do
|
70
|
+
@sequencer.pointer = @sequence.length - 1
|
71
|
+
@sequencer.step(@sequence)
|
72
|
+
assert_equal 0, @sequencer.pointer
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "#perform" do
|
78
|
+
|
79
|
+
setup do
|
80
|
+
@sequence = [1,2,3,4]
|
81
|
+
end
|
82
|
+
|
83
|
+
should "stop" do
|
84
|
+
@sequencer.trigger.expects(:stop?).once.returns(true)
|
85
|
+
@sequencer.trigger.expects(:reset?).never
|
86
|
+
@sequencer.event.expects(:do_perform).never
|
87
|
+
@sequencer.perform(@sequence)
|
88
|
+
end
|
89
|
+
|
90
|
+
should "reset and fire event" do
|
91
|
+
@sequencer.pointer = 3
|
92
|
+
@sequencer.trigger.expects(:stop?).once.returns(false)
|
93
|
+
@sequencer.trigger.expects(:reset?).once.returns(true)
|
94
|
+
@sequencer.event.expects(:do_perform).once
|
95
|
+
@sequencer.perform(@sequence)
|
96
|
+
assert_equal 0, @sequencer.pointer
|
97
|
+
end
|
98
|
+
|
99
|
+
should "fire event" do
|
100
|
+
@sequencer.pointer = 3
|
101
|
+
@sequencer.trigger.expects(:stop?).once.returns(false)
|
102
|
+
@sequencer.trigger.expects(:reset?).once.returns(false)
|
103
|
+
@sequencer.event.expects(:do_perform).once
|
104
|
+
@sequencer.perform(@sequence)
|
105
|
+
assert_equal 3, @sequencer.pointer
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
context "Functional" do
|
111
|
+
|
112
|
+
setup do
|
113
|
+
@clock = Sequencer::Clock.new(120)
|
114
|
+
@clock.event.tick { @sequencer.exec(@sequence) }
|
115
|
+
@sequencer.event.stop { @clock.stop }
|
116
|
+
@sequence = [1,2,3,4]
|
117
|
+
@cache = []
|
118
|
+
@sequencer.event.perform { | data| @cache << data }
|
119
|
+
@sequencer.trigger.stop { @sequencer.loop.count == 1 }
|
120
|
+
@clock.start(:focus => true)
|
121
|
+
end
|
122
|
+
|
123
|
+
should "create cache of sequence" do
|
124
|
+
assert_equal @sequence, @cache
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Sequencer::EventTriggerTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "EventTrigger" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@trigger = Sequencer::EventTrigger.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#reset?" do
|
12
|
+
|
13
|
+
setup do
|
14
|
+
@has_run = false
|
15
|
+
@trigger.reset { |p, d| @has_run = d }
|
16
|
+
end
|
17
|
+
|
18
|
+
should "fire stop trigger" do
|
19
|
+
assert @trigger.reset?(0, true)
|
20
|
+
assert @has_run = true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context "#reset_every" do
|
26
|
+
|
27
|
+
setup do
|
28
|
+
@trigger.reset_every(3)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "reset every three ticks" do
|
32
|
+
3.times do |i|
|
33
|
+
assert @trigger.reset?((i * 3) + 0, false)
|
34
|
+
refute @trigger.reset?((i * 3) + 1, false)
|
35
|
+
refute @trigger.reset?((i * 3) + 2, false)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#rest_every" do
|
42
|
+
|
43
|
+
setup do
|
44
|
+
@trigger.rest_every(3)
|
45
|
+
end
|
46
|
+
|
47
|
+
should "reset every three ticks" do
|
48
|
+
3.times do |i|
|
49
|
+
assert @trigger.rest?((i * 3) + 0, false)
|
50
|
+
refute @trigger.rest?((i * 3) + 1, false)
|
51
|
+
refute @trigger.rest?((i * 3) + 2, false)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#rest?" do
|
58
|
+
|
59
|
+
setup do
|
60
|
+
@has_run = false
|
61
|
+
@trigger.rest { |p, d| @has_run = d }
|
62
|
+
end
|
63
|
+
|
64
|
+
should "fire stop trigger" do
|
65
|
+
assert @trigger.rest?(0, true)
|
66
|
+
assert @has_run = true
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
context "#stop?" do
|
72
|
+
|
73
|
+
setup do
|
74
|
+
@has_run = false
|
75
|
+
@trigger.stop { |p, d| @has_run = d }
|
76
|
+
end
|
77
|
+
|
78
|
+
should "fire stop trigger" do
|
79
|
+
assert @trigger.stop?(0, true)
|
80
|
+
assert @has_run = true
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
2
|
+
$LOAD_PATH.unshift dir + "/../lib"
|
3
|
+
|
4
|
+
require "test/unit"
|
5
|
+
require "mocha/test_unit"
|
6
|
+
require "shoulda-context"
|
7
|
+
require "sequencer"
|
8
|
+
|
9
|
+
module TestHelper
|
10
|
+
|
11
|
+
def self.select_midi_output
|
12
|
+
$midi_output = UniMIDI::Output.gets
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/test/loop_test.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class Sequencer::LoopTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Loop" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@loop = Sequencer::Loop.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#to_range" do
|
12
|
+
|
13
|
+
should "convert array" do
|
14
|
+
assert_equal 1..6, @loop.send(:to_range, [1, 6])
|
15
|
+
end
|
16
|
+
|
17
|
+
should "convert number" do
|
18
|
+
assert_equal 0..9, @loop.send(:to_range, 9)
|
19
|
+
end
|
20
|
+
|
21
|
+
should "pass range" do
|
22
|
+
assert_equal 5..8, @loop.send(:to_range, 5..8)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#start" do
|
28
|
+
|
29
|
+
context "default loop" do
|
30
|
+
|
31
|
+
should "always have 0 as start" do
|
32
|
+
assert @loop.default?
|
33
|
+
assert_equal 0, @loop.start
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context "custom loop" do
|
39
|
+
|
40
|
+
setup do
|
41
|
+
@loop.range = 3..6
|
42
|
+
end
|
43
|
+
|
44
|
+
should "have custom start point" do
|
45
|
+
refute @loop.default?
|
46
|
+
assert_equal 3, @loop.start
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context "#in_bounds?" do
|
54
|
+
|
55
|
+
context "default loop" do
|
56
|
+
|
57
|
+
should "include number in range" do
|
58
|
+
assert @loop.in_bounds?(3, :length => 10)
|
59
|
+
end
|
60
|
+
|
61
|
+
should "not include number out of range" do
|
62
|
+
refute @loop.in_bounds?(10, :length => 8)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context "custom loop" do
|
68
|
+
|
69
|
+
setup do
|
70
|
+
@loop.range = 0..6
|
71
|
+
end
|
72
|
+
|
73
|
+
should "include number in range" do
|
74
|
+
assert @loop.in_bounds?(5)
|
75
|
+
end
|
76
|
+
|
77
|
+
should "not include number out of range" do
|
78
|
+
refute @loop.in_bounds?(7)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: basic-sequencer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ari Russo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: midi-topaz
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.0.15
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.0.15
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: unimidi
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.2.5
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.2.5
|
53
|
+
description: Perform a sequence of events at tempo in Ruby
|
54
|
+
email:
|
55
|
+
- ari.russo@gmail.com
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- lib/sequencer.rb
|
63
|
+
- lib/sequencer/clock.rb
|
64
|
+
- lib/sequencer/core.rb
|
65
|
+
- lib/sequencer/event.rb
|
66
|
+
- lib/sequencer/event_trigger.rb
|
67
|
+
- lib/sequencer/loop.rb
|
68
|
+
- test/clock_test.rb
|
69
|
+
- test/core_test.rb
|
70
|
+
- test/event_trigger_test.rb
|
71
|
+
- test/helper.rb
|
72
|
+
- test/loop_test.rb
|
73
|
+
homepage: http://github.com/arirusso/sequencer
|
74
|
+
licenses:
|
75
|
+
- Apache 2.0
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 1.3.6
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: basic-sequencer
|
93
|
+
rubygems_version: 2.2.2
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Perform a sequence of events at tempo
|
97
|
+
test_files: []
|