launchpad 0.2.0 → 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.
@@ -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