launchpad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ rdoc
2
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Thomas Jachmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,26 @@
1
+ = launchpad
2
+
3
+ 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.
4
+
5
+ This is the first version, nothing sophisticated, but you can access the launchpad already. In and out. The API might change quite a bit during the next releases, so don't rely on it, this is work in progress. If you need anything or think the interface could be improved in any way, please contact me.
6
+
7
+ Sometimes, the launchpad won't react to anything. Don't despair, just dis- and reconnect the thing. It seems that some (unexpected) MIDI signals make it hickup.
8
+
9
+
10
+ == Requirements
11
+
12
+ * Jan Krutisch's portmidi gem
13
+ * Roger B. Dannenberg's portmidi library
14
+
15
+
16
+ == Near future plans
17
+
18
+ * dedicated interaction with control buttons (top and right row)
19
+ * listeners for presses on single buttons/button areas
20
+ * double buffering (already there but has some glitches)
21
+ * bitmap rendering
22
+
23
+
24
+ == Copyright
25
+
26
+ Copyright (c) 2009 Thomas Jachmann. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require File.join(File.dirname(__FILE__), 'lib', 'launchpad', 'version')
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = 'launchpad'
10
+ gem.summary = 'A gem for accessing novation\'s launchpad programmatically and easily.'
11
+ gem.description = '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.'
12
+ gem.email = 'tom.j@gmx.net'
13
+ gem.homepage = 'http://github.com/thomasjachmann/launchpad'
14
+ gem.version = Launchpad::VERSION
15
+ gem.authors = ['Thomas Jachmann']
16
+ gem.add_dependency('portmidi')
17
+ #gem.add_development_dependency 'thoughtbot-shoulda', '>= 0'
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts 'Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler'
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/test_*.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/test_*.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort 'RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov'
42
+ end
43
+ end
44
+
45
+ task :test => :check_dependencies
46
+
47
+ task :default => :test
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "launchpad #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
@@ -0,0 +1,20 @@
1
+ require File.join(File.dirname(__FILE__), 'setup')
2
+
3
+ l = Launchpad.new(:input => false, :output => true)
4
+
5
+ sleep 1
6
+
7
+ pos_x = pos_y = 0
8
+ 4.times do |red|
9
+ 4.times do |green|
10
+ l.single :x => pos_x, :y => pos_y, :red => red, :green => green, :mode => :buffering
11
+ l.single :x => 7 - pos_x, :y => pos_y, :red => red, :green => green, :mode => :buffering
12
+ l.single :x => pos_x, :y => 7 - pos_y, :red => red, :green => green, :mode => :buffering
13
+ l.single :x => 7 - pos_x, :y => 7 - pos_y, :red => red, :green => green, :mode => :buffering
14
+ pos_y += 1
15
+ end
16
+ pos_x += 1
17
+ pos_y = 0
18
+ end
19
+
20
+ sleep 1
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), 'setup')
2
+
3
+ Launchpad.start do |l, x, y, state|
4
+ l.single(:x => x, :y => y, :red => state ? 3 : 0)
5
+ end
data/examples/setup.rb ADDED
@@ -0,0 +1,4 @@
1
+ # normally, this is done by rubygems (or whatever you use for your library management)
2
+ require 'rubygems'
3
+ require 'portmidi'
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'launchpad')
@@ -0,0 +1,116 @@
1
+ require 'rubygems'
2
+ require 'midiator'
3
+
4
+ @midi = MIDIator::Interface.new
5
+ @midi.autodetect_driver
6
+
7
+ def note_for(x, y)
8
+ y * 16 + x
9
+ end
10
+
11
+ def on_all(&block)
12
+ (0..7).each do |row|
13
+ row_start = row * 16
14
+ (row_start..(row_start+7)).each(&block)
15
+ end
16
+ end
17
+
18
+ def rand_pos
19
+ rand(8)
20
+ end
21
+
22
+ def rand_dir
23
+ rand(3) - 1
24
+ end
25
+
26
+ def new_dir(dir, pos)
27
+ if (dir <= 0 && pos == 0) || pos == 7
28
+ new_dir = rand_dir.abs
29
+ new_dir = -new_dir if dir > 0
30
+ new_dir
31
+ else
32
+ dir
33
+ end
34
+ end
35
+
36
+ def change_dir(dir, pos, change)
37
+ if (dir <= 0 && pos == 0) || pos == 7
38
+ -dir
39
+ elsif change
40
+ case dir
41
+ when -1 then -rand(2)
42
+ when 0 then rand(2) == 0 ? -1 : 1
43
+ when 1 then rand(2)
44
+ end
45
+ else
46
+ dir
47
+ end
48
+ end
49
+
50
+ def new_pos(pos, dir)
51
+ [0, [pos + dir, 7].min].max
52
+ end
53
+
54
+ def end_it
55
+ sleep 1
56
+ final_note = note_for(@pos_x, @pos_y)
57
+ 4.times do
58
+ on_all {|note| @midi.note_on(note, 0, 1) unless note == final_note}
59
+ sleep 0.5
60
+ on_all {|note| @midi.note_on(note, 0, 16) unless note == final_note}
61
+ sleep 0.5
62
+ end
63
+ on_all {|note| @midi.note_off(note, 0)}
64
+ sleep 0.5
65
+ end
66
+
67
+ remaining_notes = []
68
+ on_all do |note|
69
+ remaining_notes << note
70
+ @midi.note_off(note, 0)
71
+ end
72
+ remaining_notes.uniq!
73
+
74
+ sleep (ARGV[0] || 0).to_i
75
+
76
+ @pos_x = rand_pos
77
+ @pos_y = rand_pos
78
+ @dir_x = rand_dir
79
+ @dir_y = rand_dir
80
+
81
+ new_note = note_for(@pos_x, @pos_y)
82
+ old_note = nil
83
+
84
+ on_all {|note| @midi.note_on(note, 0, 3)}
85
+ on_all {|note| @midi.note_off(note, 0) unless note == new_note}
86
+
87
+ sleep 2
88
+ sleep_time = 1
89
+
90
+ loop do
91
+ unless old_note == new_note || old_note.nil?
92
+ @midi.note_off(old_note, 0)
93
+ @midi.note_on(old_note, 0, 16)
94
+ @midi.note_on(new_note, 0, 3)
95
+ end
96
+ remaining_notes.delete(new_note)
97
+ end_it && break if remaining_notes.empty?
98
+ change = rand(1)
99
+ @dir_x = change_dir(@dir_x, @pos_x, change == 0)
100
+ @dir_y = change_dir(@dir_y, @pos_y, change != 0)
101
+ #@dir_x = new_dir(@dir_x, @pos_x)
102
+ #@dir_y = new_dir(@dir_y, @pos_y)
103
+ #while @dir_x == 0 && @dir_y == 0 || (old_note.nil? && (@dir_x == 0 || @dir_y == 0))
104
+ # if rand(1) == 0
105
+ # @dir_x = new_dir(@dir_x, @pos_x)
106
+ # else
107
+ # @dir_y = new_dir(@dir_y, @pos_y)
108
+ # end
109
+ #end
110
+ @pos_x = new_pos(@pos_x, @dir_x)
111
+ @pos_y = new_pos(@pos_y, @dir_y)
112
+ old_note = new_note
113
+ new_note = note_for(@pos_x, @pos_y)
114
+ sleep sleep_time
115
+ sleep_time *= 0.96
116
+ end
data/launchpad.gemspec ADDED
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{launchpad}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Thomas Jachmann"]
12
+ s.date = %q{2009-11-08}
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
+ s.email = %q{tom.j@gmx.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "examples/colors.rb",
26
+ "examples/feedback.rb",
27
+ "examples/setup.rb",
28
+ "experiments/wandering_dot.rb",
29
+ "launchpad.gemspec",
30
+ "lib/launchpad.rb",
31
+ "lib/launchpad/version.rb",
32
+ "test/helper.rb",
33
+ "test/test_launchpad-gem.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/thomasjachmann/launchpad}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.5}
39
+ s.summary = %q{A gem for accessing novation's launchpad programmatically and easily.}
40
+ s.test_files = [
41
+ "test/helper.rb",
42
+ "test/test_launchpad-gem.rb",
43
+ "examples/colors.rb",
44
+ "examples/feedback.rb",
45
+ "examples/setup.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<portmidi>, [">= 0"])
54
+ else
55
+ s.add_dependency(%q<portmidi>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<portmidi>, [">= 0"])
59
+ end
60
+ end
61
+
data/lib/launchpad.rb ADDED
@@ -0,0 +1,165 @@
1
+ class Launchpad
2
+
3
+ class LaunchpadError < StandardError; end
4
+ class NoInputAllowed < LaunchpadError; end
5
+ class NoOutputAllowed < LaunchpadError; end
6
+ class NoLocationError < LaunchpadError; end
7
+ class CommunicationError < LaunchpadError
8
+ attr_accessor :source
9
+ def initialize(e)
10
+ super(e.portmidi_error)
11
+ self.source = e
12
+ end
13
+ end
14
+
15
+ OFF = 0x80
16
+ ON = 0x90
17
+ CC = 0xB0
18
+
19
+ def initialize(opts = nil)
20
+ opts = {
21
+ :device_name => 'Launchpad',
22
+ :input => true,
23
+ :output => true
24
+ }.merge(opts || {})
25
+ Portmidi.start
26
+ if opts[:input]
27
+ input_device = Portmidi.input_devices.select {|device| device.name == opts[:device_name]}.first
28
+ @input = Portmidi::Input.new(input_device.device_id)
29
+ end
30
+ if opts[:output]
31
+ output_device = Portmidi.output_devices.select {|device| device.name == opts[:device_name]}.first
32
+ @output = Portmidi::Output.new(output_device.device_id)
33
+ reset
34
+ end
35
+ @buffering = false
36
+ end
37
+
38
+ def self.start(opts = nil, &block)
39
+ opts ||= {}
40
+ latency = (opts.delete(:latency) || 0.001).to_f
41
+ launchpad = Launchpad.new(opts.merge({:input => true, :output => true}))
42
+ loop do
43
+ messages = launchpad.input
44
+ if messages
45
+ messages.each do |message|
46
+ message = parse_message(message)
47
+ if message[:code] == ON
48
+ block.call(launchpad, message[:x], message[:y], message[:state])
49
+ end
50
+ end
51
+ end
52
+ sleep latency
53
+ end
54
+ rescue Portmidi::DeviceError => e
55
+ raise CommunicationError.new(e)
56
+ ensure
57
+ launchpad.reset if launchpad
58
+ end
59
+
60
+ # Reset the launchpad - all settings are reset and all LEDs are switched off
61
+ def reset
62
+ output(CC, 0x00, 0x00)
63
+ end
64
+
65
+ # Light all LEDs (for testing purposes)
66
+ # takes an optional parameter brightness (1-3, defaults to 3)
67
+ def light_all(brightness = 3)
68
+ output(CC, 0x00, 124 + min_max_color(brightness, false))
69
+ end
70
+
71
+ def start_buffering
72
+ output(CC, 0x00, 0x31)
73
+ @buffering = true
74
+ end
75
+
76
+ def flush_buffer(end_buffering = true)
77
+ output(CC, 0x00, 0x34)
78
+ if end_buffering
79
+ output(CC, 0x00, 0x30)
80
+ @buffering = false
81
+ end
82
+ end
83
+
84
+ # Switches a single LED
85
+ # * :x => x coordinate (0 based from top left, mandatory)
86
+ # * :y => y coordinate (0 based from top left, mandatory)
87
+ # * :red => brightness of red LED (0-3, optional, defaults to 0)
88
+ # * :green => brightness of red LED (0-3, optional, defaults to 0)
89
+ # * :mode => button behaviour (:normal, :flashing, :buffering, optional, defaults to :normal)
90
+ def single(opts)
91
+ location = location(opts)
92
+ velocity = velocity(opts)
93
+ output(ON, location, velocity)
94
+ end
95
+
96
+ def multi(*velocities)
97
+ output(CC, 0x01, 0x00)
98
+ output(0x92, *velocities)
99
+ end
100
+
101
+ # Switches LEDs marked as flashing on (when using custom timer for flashing)
102
+ def custom_flashing_on
103
+ output(CC, 0x00, 0x20)
104
+ end
105
+
106
+ # Switches LEDs marked as flashing off (when using custom timer for flashing)
107
+ def custom_flashing_off
108
+ output(CC, 0x00, 0x21)
109
+ end
110
+
111
+ # Starts flashing LEDs marked as flashing automatically
112
+ def start_auto_flashing
113
+ output(CC, 0x00, 0x28)
114
+ end
115
+
116
+ # Stops flashing LEDs marked as flashing automatically (turning them on)
117
+ alias_method :stop_auto_flashing, :custom_flashing_on
118
+
119
+ def coordinates(location)
120
+ [location % 16, location / 16]
121
+ end
122
+
123
+ def input
124
+ raise NoInputAllowed if @input.nil?
125
+ @input.read(16)
126
+ end
127
+
128
+ def output(*args)
129
+ raise NoOutputAllowed if @output.nil?
130
+ @output.write([{:message => args, :timestamp => 0}])
131
+ end
132
+
133
+ private
134
+
135
+ def self.parse_message(message)
136
+ message = message[:message]
137
+ {
138
+ :code => message[0],
139
+ :x => message[1] % 16,
140
+ :y => message[1] / 16,
141
+ :state => message[2] == 127
142
+ }
143
+ end
144
+
145
+ def location(opts)
146
+ raise NoLocationError.new('you need to specify a location (x/y, 0 based from top left)') if (y = opts[:y]).nil? || (x = opts[:x]).nil?
147
+ y * 16 + x
148
+ end
149
+
150
+ def velocity(opts)
151
+ red = min_max_color(opts[:red] || 0)
152
+ green = min_max_color(opts[:green] || 0)
153
+ flags = case opts[:mode]
154
+ when :flashing then 8
155
+ when :buffering then 0
156
+ else 12
157
+ end
158
+ (16 * (green)) + red + flags
159
+ end
160
+
161
+ def min_max_color(color, with_off = true)
162
+ [[with_off ? 0 : 1, color.to_i].max, 3].min
163
+ end
164
+
165
+ end
@@ -0,0 +1,3 @@
1
+ class Launchpad
2
+ VERSION = '0.0.1'
3
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ #require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'launchpad'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestLaunchpadGem < Test::Unit::TestCase
4
+ #should "probably rename this file and start testing for real" do
5
+ # flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ #end
7
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: launchpad
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Jachmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-08 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: portmidi
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: 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.
26
+ email: tom.j@gmx.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - examples/colors.rb
41
+ - examples/feedback.rb
42
+ - examples/setup.rb
43
+ - experiments/wandering_dot.rb
44
+ - launchpad.gemspec
45
+ - lib/launchpad.rb
46
+ - lib/launchpad/version.rb
47
+ - test/helper.rb
48
+ - test/test_launchpad-gem.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/thomasjachmann/launchpad
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.5
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A gem for accessing novation's launchpad programmatically and easily.
77
+ test_files:
78
+ - test/helper.rb
79
+ - test/test_launchpad-gem.rb
80
+ - examples/colors.rb
81
+ - examples/feedback.rb
82
+ - examples/setup.rb