launchpad 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -67,10 +67,16 @@ For more details, see the examples. examples/color_picker.rb is the most complex
67
67
 
68
68
  * interaction responses for presses on single grid buttons/button areas
69
69
  * bitmap rendering
70
+ * internal tracking of LED states for both buffers
70
71
 
71
72
 
72
73
  == Changelog
73
74
 
75
+ === v.0.2.1
76
+
77
+ * Launchpad::Interaction#close now properly stops interaction first
78
+ * multi threading: Launchpad::Interaction#start method can be called with :detached => true to allow calling thread to continue
79
+
74
80
  === v.0.2.0
75
81
 
76
82
  * double buffering (see Launchpad::Device#buffering_mode)
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ begin
14
14
  gem.version = Launchpad::VERSION
15
15
  gem.authors = ['Thomas Jachmann']
16
16
  gem.has_rdoc = true
17
- gem.add_dependency('portmidi')
17
+ gem.add_dependency('portmidi', '>= 0.0.6')
18
18
  gem.add_development_dependency('thoughtbot-shoulda', '>= 0')
19
19
  gem.add_development_dependency('mocha')
20
20
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
@@ -1,9 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), 'setup')
2
2
 
3
- # need to declare as instance variables here for being able to access
4
- # @interaction within interactors created by create_interactor_block
5
- device = Launchpad::Device.new
6
- interaction = Launchpad::Interaction.new(:device => device)
3
+ interaction = Launchpad::Interaction.new
7
4
 
8
5
  # build color arrays for color display views
9
6
  colors_single = [
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{launchpad}
8
- s.version = "0.2.0"
8
+ s.version = "0.2.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Thomas Jachmann"]
12
- s.date = %q{2009-11-24}
12
+ s.date = %q{2010-01-24}
13
13
  s.description = %q{This gem provides an interface to access novation's launchpad programmatically. LEDs can be lighted and button presses can be evaluated using launchpad's MIDI input/output.}
14
14
  s.email = %q{tom.j@gmx.net}
15
15
  s.extra_rdoc_files = [
@@ -66,16 +66,16 @@ Gem::Specification.new do |s|
66
66
  s.specification_version = 3
67
67
 
68
68
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
69
- s.add_runtime_dependency(%q<portmidi>, [">= 0"])
69
+ s.add_runtime_dependency(%q<portmidi>, [">= 0.0.6"])
70
70
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
71
71
  s.add_development_dependency(%q<mocha>, [">= 0"])
72
72
  else
73
- s.add_dependency(%q<portmidi>, [">= 0"])
73
+ s.add_dependency(%q<portmidi>, [">= 0.0.6"])
74
74
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
75
75
  s.add_dependency(%q<mocha>, [">= 0"])
76
76
  end
77
77
  else
78
- s.add_dependency(%q<portmidi>, [">= 0"])
78
+ s.add_dependency(%q<portmidi>, [">= 0.0.6"])
79
79
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
80
80
  s.add_dependency(%q<mocha>, [">= 0"])
81
81
  end
@@ -1,4 +1,3 @@
1
- require 'launchpad/device'
2
1
  require 'launchpad/interaction'
3
2
 
4
3
  # All the fun of launchpad in one module!
@@ -145,7 +145,10 @@ module Launchpad
145
145
  # * Hash:
146
146
  # [<tt>:red</tt>] brightness of red LED
147
147
  # [<tt>:green</tt>] brightness of green LED
148
- # [<tt>:mode</tt>] button mode
148
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
149
+ # [<tt>:normal/tt>] updates the LEDs for all circumstances (the new value will be written to both buffers)
150
+ # [<tt>:flashing/tt>] updates the LEDs for flashing (the new values will be written to buffer 0 while the LEDs will be off in buffer 1, see buffering_mode)
151
+ # [<tt>:buffering/tt>] updates the LEDs for the current update_buffer only
149
152
  # the array consists of 64 colors for the grid buttons,
150
153
  # 8 colors for the scene buttons (top to bottom)
151
154
  # and 8 colors for the top control buttons (left to right),
@@ -53,7 +53,13 @@ module Launchpad
53
53
  end
54
54
 
55
55
  # Closes the interaction's device - nothing can be done with the interaction/device afterwards.
56
+ #
57
+ # Errors raised:
58
+ #
59
+ # [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
60
+ # [Launchpad::CommunicationError] when anything unexpected happens while communicating with the
56
61
  def close
62
+ stop
57
63
  @device.close
58
64
  end
59
65
 
@@ -62,27 +68,55 @@ module Launchpad
62
68
  @device.closed?
63
69
  end
64
70
 
65
- # Starts interacting with the launchpad, blocking. Resets the device when
66
- # the interaction was properly stopped via stop.
71
+ # Starts interacting with the launchpad. Resets the device when
72
+ # the interaction was properly stopped via stop or close.
73
+ #
74
+ # Optional options hash:
75
+ #
76
+ # [<tt>:detached</tt>] <tt>true/false</tt>,
77
+ # whether to detach the interaction, method is blocking when +false+
78
+ # optional, defaults to +false+
67
79
  #
68
80
  # Errors raised:
69
81
  #
70
82
  # [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
83
+ # [Launchpad::NoOutputAllowedError] when output is not enabled on the interaction's device
71
84
  # [Launchpad::CommunicationError] when anything unexpected happens while communicating with the launchpad
72
- def start
85
+ def start(opts = nil)
86
+ opts = {
87
+ :detached => false
88
+ }.merge(opts || {})
73
89
  @active = true
74
- while @active do
75
- @device.read_pending_actions.each {|action| respond_to_action(action)}
76
- sleep @latency unless @latency <= 0
90
+ @reader_thread ||= Thread.new do
91
+ begin
92
+ while @active do
93
+ @device.read_pending_actions.each {|action| respond_to_action(action)}
94
+ sleep @latency unless @latency <= 0
95
+ end
96
+ rescue Portmidi::DeviceError => e
97
+ raise CommunicationError.new(e)
98
+ ensure
99
+ @device.reset
100
+ end
77
101
  end
78
- @device.reset
79
- rescue Portmidi::DeviceError => e
80
- raise CommunicationError.new(e)
102
+ @reader_thread.join unless opts[:detached]
81
103
  end
82
104
 
83
- # Stops interacting with the launchpad and resets it.
105
+ # Stops interacting with the launchpad.
106
+ #
107
+ # Errors raised:
108
+ #
109
+ # [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
110
+ # [Launchpad::CommunicationError] when anything unexpected happens while communicating with the
84
111
  def stop
85
112
  @active = false
113
+ if @reader_thread
114
+ # run (resume from sleep) and wait for @reader_thread to end
115
+ @reader_thread.run if @reader_thread.alive?
116
+ @reader_thread.join
117
+ @reader_thread = nil
118
+ end
119
+ nil
86
120
  end
87
121
 
88
122
  # Registers a response to one or more actions.
@@ -1,3 +1,3 @@
1
1
  module Launchpad
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -16,6 +16,11 @@ class TestInteraction < Test::Unit::TestCase
16
16
  assert_equal 'device', Launchpad::Interaction.new(:device_name => 'device').device
17
17
  end
18
18
 
19
+ should 'create device with given input_device_id/output_device_id' do
20
+ Launchpad::Device.expects(:new).with(:input_device_id => 'in', :output_device_id => 'out', :input => true, :output => true).returns('device')
21
+ assert_equal 'device', Launchpad::Interaction.new(:input_device_id => 'in', :output_device_id => 'out').device
22
+ end
23
+
19
24
  should 'initialize device if given' do
20
25
  assert_equal 'device', Launchpad::Interaction.new(:device => 'device').device
21
26
  end
@@ -28,18 +33,17 @@ class TestInteraction < Test::Unit::TestCase
28
33
 
29
34
  context 'close' do
30
35
 
31
- should 'close device' do
32
- interaction = Launchpad::Interaction.new(:device => device = Launchpad::Device.new)
33
- device.expects(:close)
36
+ should 'not be active' do
37
+ interaction = Launchpad::Interaction.new
38
+ interaction.start(:detached => true)
34
39
  interaction.close
40
+ assert !interaction.active
35
41
  end
36
42
 
37
- should 'craise NoInputAllowedError on subsequent accesses' do
43
+ should 'close device' do
38
44
  interaction = Launchpad::Interaction.new(:device => device = Launchpad::Device.new)
45
+ device.expects(:close)
39
46
  interaction.close
40
- assert_raise Launchpad::NoInputAllowedError do
41
- interaction.start
42
- end
43
47
  end
44
48
 
45
49
  end
@@ -57,27 +61,45 @@ class TestInteraction < Test::Unit::TestCase
57
61
 
58
62
  context 'start' do
59
63
 
60
- # this is kinda greybox tested, since I couldn't come up with another way to test a loop [thomas, 2009-11-11]
61
-
62
64
  setup do
63
65
  @interaction = Launchpad::Interaction.new(:device => @device = Launchpad::Device.new)
64
66
  end
65
67
 
66
- context 'up until read_pending_actions' do
67
-
68
- setup do
69
- @device.stubs(:read_pending_actions).raises(BreakError)
70
- end
71
-
72
- should 'set active to true' do
73
- begin
74
- @interaction.start
75
- fail 'should raise BreakError'
76
- rescue BreakError
77
- assert @interaction.active
78
- end
68
+ teardown do
69
+ begin
70
+ @interaction.close
71
+ rescue
72
+ # ignore, should be handled in tests, this is just to close all the spawned threads
79
73
  end
80
-
74
+ end
75
+
76
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
77
+ should 'set active to true in blocking mode' do
78
+ t = Thread.new {}
79
+ Thread.expects(:new).returns(t)
80
+ @interaction.start
81
+ assert @interaction.active
82
+ end
83
+
84
+ should 'set active to true in detached mode' do
85
+ @interaction.start(:detached => true)
86
+ assert @interaction.active
87
+ end
88
+
89
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
90
+ should 'start a new thread and block in blocking mode' do
91
+ t = Thread.new {}
92
+ Thread.expects(:new).returns(t)
93
+ t.expects(:join)
94
+ @interaction.start
95
+ end
96
+
97
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
98
+ should 'start a new thread and return in detached mode' do
99
+ t = Thread.new {}
100
+ Thread.expects(:new).returns(t)
101
+ t.expects(:join).never
102
+ @interaction.start(:detached => true)
81
103
  end
82
104
 
83
105
  should 'raise CommunicationError when Portmidi::DeviceError occurs' do
@@ -87,18 +109,16 @@ class TestInteraction < Test::Unit::TestCase
87
109
  end
88
110
  end
89
111
 
112
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
90
113
  should 'call respond_to_action with actions from respond_to_action' do
91
- begin
92
- @interaction.stubs(:sleep).raises(BreakError)
93
- @device.stubs(:read_pending_actions).returns(['message1', 'message2'])
94
- @interaction.expects(:respond_to_action).with('message1').once
95
- @interaction.expects(:respond_to_action).with('message2').once
96
- @interaction.start
97
- fail 'should raise BreakError'
98
- rescue BreakError
99
- end
114
+ @interaction.stubs(:sleep).raises(BreakError)
115
+ @device.stubs(:read_pending_actions).returns(['message1', 'message2'])
116
+ @interaction.expects(:respond_to_action).with('message1').once
117
+ @interaction.expects(:respond_to_action).with('message2').once
118
+ @interaction.start(:detached => true)
100
119
  end
101
120
 
121
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
102
122
  context 'sleep' do
103
123
 
104
124
  setup do
@@ -106,52 +126,101 @@ class TestInteraction < Test::Unit::TestCase
106
126
  end
107
127
 
108
128
  should 'sleep with default latency of 0.001 when none given' do
109
- begin
129
+ assert_raise BreakError do
110
130
  @interaction.expects(:sleep).with(0.001).raises(BreakError)
111
131
  @interaction.start
112
- fail 'should raise BreakError'
113
- rescue BreakError
114
132
  end
115
133
  end
116
134
 
117
135
  should 'sleep with given latency' do
118
- begin
136
+ assert_raise BreakError do
119
137
  @interaction = Launchpad::Interaction.new(:latency => 4, :device => @device)
120
138
  @interaction.expects(:sleep).with(4).raises(BreakError)
121
139
  @interaction.start
122
- fail 'should raise BreakError'
123
- rescue BreakError
124
140
  end
125
141
  end
126
142
 
127
143
  should 'sleep with absolute value of given negative latency' do
128
- begin
144
+ assert_raise BreakError do
129
145
  @interaction = Launchpad::Interaction.new(:latency => -3.1, :device => @device)
130
146
  @interaction.expects(:sleep).with(3.1).raises(BreakError)
131
147
  @interaction.start
132
- fail 'should raise BreakError'
133
- rescue BreakError
134
148
  end
135
149
  end
136
150
 
137
- should 'not sleep when latency is <= 0' # TODO don't know how to test this [thomas, 2009-11-11]
151
+ should 'not sleep when latency is 0' do
152
+ @interaction = Launchpad::Interaction.new(:latency => 0, :device => @device)
153
+ @interaction.expects(:sleep).never
154
+ @interaction.start(:detached => true)
155
+ end
138
156
 
139
157
  end
140
158
 
141
- should 'reset the device after the loop' # TODO don't know how to test this [thomas, 2009-11-11]
159
+ should 'reset the device after the loop' do
160
+ @interaction.device.expects(:reset)
161
+ @interaction.start(:detached => true)
162
+ @interaction.stop
163
+ end
164
+
165
+ should 'raise NoOutputAllowedError on closed interaction' do
166
+ @interaction.close
167
+ assert_raise Launchpad::NoOutputAllowedError do
168
+ @interaction.start
169
+ end
170
+ end
142
171
 
143
172
  end
144
173
 
145
174
  context 'stop' do
146
175
 
147
- should 'set active to false' do
176
+ should 'set active to false in blocking mode' do
177
+ i = Launchpad::Interaction.new
178
+ Thread.new do
179
+ i.start
180
+ end
181
+ assert i.active
182
+ i.stop
183
+ assert !i.active
184
+ end
185
+
186
+ should 'set active to false in detached mode' do
148
187
  i = Launchpad::Interaction.new
149
- i.instance_variable_set('@active', true)
188
+ i.start(:detached => true)
150
189
  assert i.active
151
190
  i.stop
152
191
  assert !i.active
153
192
  end
154
193
 
194
+ should 'be callable anytime' do
195
+ i = Launchpad::Interaction.new
196
+ i.stop
197
+ i.start(:detached => true)
198
+ i.stop
199
+ i.stop
200
+ end
201
+
202
+ # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
203
+ should 'call run and join on a running reader thread' do
204
+ t = Thread.new {sleep}
205
+ Thread.expects(:new).returns(t)
206
+ t.expects(:run)
207
+ t.expects(:join)
208
+ i = Launchpad::Interaction.new
209
+ i.start(:detached => true)
210
+ i.stop
211
+ end
212
+
213
+ # this is kinda greybox tested, since I couldn't come up with another way to test tread handling [thomas, 2010-01-24]
214
+ should 'raise pending exceptions in detached mode' do
215
+ t = Thread.new {raise BreakError}
216
+ Thread.expects(:new).returns(t)
217
+ i = Launchpad::Interaction.new
218
+ i.start(:detached => true)
219
+ assert_raise BreakError do
220
+ i.stop
221
+ end
222
+ end
223
+
155
224
  end
156
225
 
157
226
  context 'response_to/no_response_to/respond_to' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchpad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Jachmann
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-24 00:00:00 +01:00
12
+ date: 2010-01-24 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: "0"
23
+ version: 0.0.6
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thoughtbot-shoulda