arcenciel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ Gemfile.lock
@@ -0,0 +1 @@
1
+ This project is dedicated to Ben Hughes.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,116 @@
1
+ ## Arcenciel: Physical Knobs for Virtual Machines
2
+
3
+ ![Hero](docs/images/hero.jpg)
4
+
5
+ *"Touch the cloud; feel its thunder beneath your fingertips."*
6
+
7
+ ### Introduction
8
+
9
+ Arcenciel is a declarative microframework for the [Monome](http://monome.org/) Arc OSC controller.
10
+
11
+ All sufficiently complex systems have *phases* and *transitions*.
12
+ Discovering the boundary of each is challenging and often defies intuition.
13
+ Trajectories can be stable, metastable, or chaotic.
14
+ Small variations in the parameters of the system can manifest as large, unexpected effects.
15
+ How can we thoroughly and naturally explore the space of all states?
16
+
17
+ Consider this idea: Play your benchmarks like a *musical instrument*.
18
+
19
+ Here's an [inspiring example](https://vimeo.com/21596928) of the Arc's potential.
20
+
21
+ *Monome has discontinued all models of the Arc controller and the supply is therefore strictly limited.*
22
+ *Each unit is a stunningly beautiful masterpiece: a rare combination of technology and aesthetic joy.*
23
+ *If you're among the lucky few who own this extraordinary recherché, love and cherish it forever.*
24
+ *The Arc's value handily surpasses its weight in gold.*
25
+
26
+ ### Dependencies
27
+
28
+ Arcenciel uses the OSC protocol (UDP).
29
+ SerialOSC interfaces each Arc with the host machine.
30
+
31
+ To get started on Mac OS X, you'll need to:
32
+
33
+ * Install the [FTDI virtual COM port (VCP) driver](http://www.ftdichip.com/Drivers/VCP.htm).
34
+ * Install the [SerialOSC server](https://github.com/monome/serialosc/releases/tag/1.2).
35
+ * Restart your computer.
36
+
37
+ Test your configuration by running the bundled demonstration script:
38
+
39
+ ```
40
+ $ gem install arcenciel
41
+ $ arcenciel-demo
42
+ [ARC] Added device (m0000171; UDP 19930).
43
+ [ARC] Assigning control 'Arc' to device...
44
+ [ARC] Illuminated encoder 'First' (0). Press any key.
45
+ [ARC] Illuminated encoder 'Second' (1). Press any key.
46
+ [ARC] Assigned control 'Arc'.
47
+ ```
48
+
49
+ ### Usage
50
+
51
+ The Arc reacts to tactile interactions with its rotary encoders:
52
+
53
+ * Angular motion
54
+ * Threshold pressure
55
+
56
+ From events generated by the device, Arcenciel emulates *logical knobs*.
57
+ Each knob has a distinct configuration: a name, a value type, a range, and a precision (degrees per sweep).
58
+ When the value of a knob changes, the provided callback is invoked.
59
+
60
+ Arcenciel discovers all connected devices and assigns each to a logical controller (defining one or more knobs).
61
+ When a device is assigned to a controller, for each of its knobs, Arcenciel illuminates the rotary encoder's ring and requests that you to confirm the assignment.
62
+
63
+ Knobs are defined using these attributes:
64
+
65
+ * Name
66
+ * Initial value
67
+ * Minimum and maximum value (individually or as a range)
68
+ * Sweep (degrees of rotation for the entire range)
69
+ * Value type (integer or float)
70
+
71
+ ### Example
72
+
73
+ Here's a sample that targets a single Arc (two-encoder version).
74
+
75
+ * Emulate two knobs: "Query rate" and "Rows scanned."
76
+ * Use a distinct range and sweep for each knob, with integer values.
77
+ * Invoke a distinct callback for each knob.
78
+
79
+ Implementation:
80
+
81
+ ```
82
+ require 'arcenciel'
83
+
84
+ Arcenciel.run! do
85
+ knob do
86
+ name "Query rate"
87
+
88
+ min 0
89
+ max 100
90
+ type :integer
91
+ sweep 1440
92
+
93
+ on_value do |rate|
94
+ puts "A: #{name}: #{rate}"
95
+ end
96
+ end
97
+
98
+ knob do
99
+ name "Rows scanned"
100
+
101
+ min 0
102
+ max 10000
103
+ type :integer
104
+ sweep 360
105
+
106
+ on_value do |rows|
107
+ puts "B: #{name}: #{rows}"
108
+ end
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### Special Thanks
114
+
115
+ * The Monome minimalists, for a solid concept and outstanding craftsmanship.
116
+ * The generous dudes who sold me their Arcs, without which this project would not be possible.
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require 'arcenciel/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'arcenciel'
8
+ s.version = Arcenciel::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ['Nelson Gauthier']
11
+ s.email = ['nelson.gauthier@gmail.com']
12
+ s.homepage = 'https://github.com/nelgau/arcenciel'
13
+ s.summary = "Declarative Monome Arc microframework"
14
+ s.description = "Physical knobs for virtual machines"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.executables = ['arcenciel-demo']
18
+ s.require_path = 'lib'
19
+
20
+ s.add_runtime_dependency 'osc-ruby', '~> 1.1.1'
21
+ s.add_runtime_dependency 'colored', '>= 1.2.0'
22
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require 'arcenciel'
3
+
4
+ Arcenciel.run! do
5
+ knob do
6
+ name 'First'
7
+ end
8
+
9
+ knob do
10
+ name 'Second'
11
+ end
12
+ end
Binary file
@@ -0,0 +1,30 @@
1
+ require 'osc-ruby'
2
+
3
+ require 'arcenciel/utility'
4
+ require 'arcenciel/manager'
5
+ require 'arcenciel/surfaces'
6
+
7
+ module Arcenciel
8
+
9
+ # Lists all controllers.
10
+ def self.controllers
11
+ @controllers ||= []
12
+ end
13
+
14
+ # Add a new controllers.
15
+ def self.add(&blk)
16
+ controllers << Surfaces::Controller.from_dsl(&blk)
17
+ end
18
+
19
+ # Run the main event loop.
20
+ def self.run!(&blk)
21
+ add(&blk) if block_given?
22
+ Manager.run!(controllers)
23
+ end
24
+
25
+ # Set the controller lifecycle logger.
26
+ def self.logger=(logger)
27
+ Logging.logger = logger
28
+ end
29
+
30
+ end
@@ -0,0 +1,187 @@
1
+ require 'thread'
2
+
3
+ require 'arcenciel/manager/device'
4
+ require 'arcenciel/manager/hub'
5
+
6
+ module Arcenciel
7
+ class Manager
8
+ include Logging
9
+
10
+ attr_reader :controllers
11
+ attr_reader :devices
12
+
13
+ def self.run!(controllers)
14
+ new(controllers).run!
15
+ end
16
+
17
+ def initialize(controllers)
18
+ @controllers = controllers
19
+
20
+ @hub = Hub.new
21
+ @mutex = Mutex.new
22
+ @shutdown = false
23
+
24
+ @id_map = {}
25
+ @port_map = {}
26
+ end
27
+
28
+ def run!
29
+ trap_signals
30
+
31
+ add_listeners
32
+ list_devices
33
+ begin_notify
34
+
35
+ start_hub
36
+ run_loop
37
+ ensure
38
+ clear_devices
39
+ end
40
+
41
+ def shutdown!
42
+ @shutdown = true
43
+ end
44
+
45
+ def shutdown?
46
+ !!@shutdown
47
+ end
48
+
49
+ private
50
+
51
+ def trap_signals
52
+ Signal.trap('INT') { shutdown! }
53
+ Signal.trap('TERM') { shutdown! }
54
+ end
55
+
56
+ def run_loop
57
+ until shutdown?
58
+ assign_devices
59
+ sleep 0.1
60
+ end
61
+ end
62
+
63
+ def devices
64
+ @id_map.values
65
+ end
66
+
67
+ def assign_devices
68
+ devices.each do |device|
69
+ next if device.attached?
70
+ begin
71
+ if controller = controllers.first(&:assigned?)
72
+ controller.assign!(device)
73
+ end
74
+ rescue Device::InvalidDeviceError
75
+ controller.unassign!
76
+ end
77
+ end
78
+ end
79
+
80
+ def clear_devices
81
+ devices.each do |device|
82
+ begin
83
+ device.ring_clear_all
84
+ rescue
85
+ end
86
+ end
87
+ end
88
+
89
+ def start_hub
90
+ @hub.run!
91
+ end
92
+
93
+ def add_listeners
94
+ listen('/serialosc/add', :process_add)
95
+ listen('/serialosc/remove', :process_remove)
96
+ listen('/serialosc/device', :process_device)
97
+ listen('/arc/enc/delta', :process_delta)
98
+ listen('/arc/enc/key', :process_key)
99
+ end
100
+
101
+ def listen(path, name)
102
+ @hub.listen(path, &method(name))
103
+ end
104
+
105
+ def list_devices
106
+ @hub.send('/serialosc/list')
107
+ end
108
+
109
+ def begin_notify
110
+ @hub.send('/serialosc/notify')
111
+ end
112
+
113
+ def add_device(device)
114
+ return if @id_map.include?(device.id)
115
+
116
+ @id_map[device.id] = device
117
+ @port_map[device.port] = device
118
+ device.start!(@hub.server_port)
119
+ end
120
+
121
+ def remove_device(id)
122
+ if device = @id_map.delete(id)
123
+ @port_map.delete(device.port)
124
+ device.stop!
125
+ end
126
+ end
127
+
128
+ def dispatch_delta(port, index, delta)
129
+ if device = @port_map[port]
130
+ device.on_delta(index, delta)
131
+ end
132
+ end
133
+
134
+ def dispatch_key(port, index, state)
135
+ if device = @port_map[port]
136
+ device.on_key(index, state)
137
+ end
138
+ end
139
+
140
+ def process_add(msg)
141
+ list_devices
142
+ begin_notify
143
+ end
144
+
145
+ def process_remove(msg)
146
+ id = msg.to_a[0]
147
+
148
+ @mutex.synchronize do
149
+ remove_device(id)
150
+ begin_notify
151
+ end
152
+ end
153
+
154
+ def process_device(msg)
155
+ id, type, port = msg.to_a
156
+ device = Device.new(id, type, port)
157
+
158
+ if device.arc?
159
+ @mutex.synchronize do
160
+ add_device(device)
161
+ end
162
+ end
163
+ end
164
+
165
+ def process_delta(msg)
166
+ port = msg.ip_port
167
+ args = msg.to_a
168
+ index = args[0].to_i
169
+ delta = args[1].to_i
170
+
171
+ @mutex.synchronize do
172
+ dispatch_delta(port, index, delta)
173
+ end
174
+ end
175
+
176
+ def process_key(msg)
177
+ port = msg.ip_port
178
+ args = msg.to_a
179
+ index = args[0].to_i
180
+ state = args[1].to_i
181
+
182
+ @mutex.synchronize do
183
+ dispatch_key(port, index, state)
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,120 @@
1
+ module Arcenciel
2
+ class Device
3
+ class InvalidDeviceError < StandardError; end
4
+
5
+ include Logging
6
+
7
+ attr_reader :id
8
+ attr_reader :type
9
+ attr_reader :port
10
+ attr_reader :size
11
+
12
+ attr_reader :controller
13
+
14
+ def initialize(id, type, port)
15
+ @id = id
16
+ @type = type
17
+ @port = port
18
+ @is_arc, @size = parse_type(type)
19
+
20
+ @valid = false
21
+ @controller = nil
22
+
23
+ @client = OSC::Client.new('localhost', port)
24
+ end
25
+
26
+ def arc?
27
+ !!@is_arc
28
+ end
29
+
30
+ def attached?
31
+ !!@controller
32
+ end
33
+
34
+ def valid?
35
+ !!@valid
36
+ end
37
+
38
+ def validate!
39
+ raise InvalidDeviceError unless valid?
40
+ end
41
+
42
+ def start!(server_port)
43
+ set_destination(server_port)
44
+ @valid = true
45
+ ring_clear_all
46
+
47
+ log_info "Added device (#{id}; UDP #{port})."
48
+ end
49
+
50
+ def stop!
51
+ unassign_controller!
52
+ @valid = false
53
+
54
+ log_warn "Removed device (#{id}; UDP #{port})."
55
+ end
56
+
57
+ def attach!(controller)
58
+ validate!
59
+ @controller = controller
60
+ end
61
+
62
+ def ring_clear_all
63
+ validate!
64
+ (0...size).each do |i|
65
+ ring_clear(i)
66
+ end
67
+ end
68
+
69
+ def ring_clear(index)
70
+ validate!
71
+ ring_all(index, 0)
72
+ end
73
+
74
+ def ring_set(index, x, level)
75
+ validate!
76
+ @client.send(OSC::Message.new('/arc/ring/set', index, x, level))
77
+ end
78
+
79
+ def ring_all(index, level)
80
+ validate!
81
+ @client.send(OSC::Message.new('/arc/ring/all', index, level))
82
+ end
83
+
84
+ def ring_map(index, array)
85
+ validate!
86
+ @client.send(OSC::Message.new('/arc/ring/map', index, *array))
87
+ end
88
+
89
+ def ring_range(index, x1, x2, level)
90
+ validate!
91
+ @client.send(OSC::Message.new('/arc/ring/range', index, x1, x2, level))
92
+ end
93
+
94
+ def on_delta(index, delta)
95
+ @controller && @controller.on_delta(index, delta)
96
+ end
97
+
98
+ def on_key(index, state)
99
+ @controller && @controller.on_key(index, state)
100
+ end
101
+
102
+ private
103
+
104
+ def set_destination(port)
105
+ @client.send(OSC::Message.new('/sys/prefix', 'arc'))
106
+ @client.send(OSC::Message.new('/sys/host', 'localhost'))
107
+ @client.send(OSC::Message.new('/sys/port', port))
108
+ end
109
+
110
+ def unassign_controller!
111
+ @controller && @controller.unassign!
112
+ @controller = nil
113
+ end
114
+
115
+ def parse_type(type)
116
+ m = type.match(/monome arc (\d+)/)
117
+ m ? [true , m[1].to_i] : [false, nil]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ require 'thread'
2
+
3
+ module Arcenciel
4
+ class Hub
5
+ attr_reader :serial_port
6
+ attr_reader :server_port
7
+
8
+ attr_reader :client
9
+ attr_reader :server
10
+
11
+ def initialize
12
+ @serial_port = 12002
13
+ @server_port = 10210
14
+
15
+ @client = OSC::Client.new('localhost', serial_port)
16
+ @server = OSC::Server.new(server_port)
17
+ end
18
+
19
+ def run!
20
+ Thread.start do
21
+ server.run
22
+ end
23
+ end
24
+
25
+ def send(command)
26
+ client.send(OSC::Message.new(command, 'localhost', server_port))
27
+ end
28
+
29
+ def listen(path, &blk)
30
+ server.add_method(path, &blk)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,109 @@
1
+ require 'arcenciel/surfaces/controller'
2
+ require 'arcenciel/surfaces/knob'
3
+
4
+ module Arcenciel
5
+ module Surfaces
6
+
7
+ class Controller
8
+
9
+ class DSL < DSLBase
10
+
11
+ # Set the name of this logical controller.
12
+ def name(name)
13
+ opts[:name] = name
14
+ end
15
+
16
+ # Add a new logical knob (encoder) to the controller.
17
+ def knob(&blk)
18
+ opts[:knobs] ||= []
19
+ opts[:knobs] << Knob.from_dsl(&blk)
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ class Knob
27
+
28
+ class DSL < DSLBase
29
+
30
+ # Set the name of this logical knob (encoder).
31
+ def name(name)
32
+ opts[:name] = name
33
+ end
34
+
35
+ # Set the initial value of the knob.
36
+ # Default - Minimum value
37
+ def initial(value)
38
+ opts[:initial] = value
39
+ end
40
+
41
+ # Set the range of values for the knob.
42
+ def range(range)
43
+ opts[:min] = range.begin
44
+ opts[:max] = range.end
45
+ end
46
+
47
+ # Set the minimum value of the knob.
48
+ # Default - 0
49
+ def min(value)
50
+ opts[:min] = value
51
+ end
52
+
53
+ # Set the maximum value of the knob.
54
+ # Default - 100
55
+ def max(value)
56
+ opts[:max] = value
57
+ end
58
+
59
+ # Set the type of value (:integer or :float).
60
+ # Default - :float
61
+ def type(type)
62
+ opts[:type] = type
63
+ end
64
+
65
+ # Set the precision of the knob (degrees per sweep).
66
+ # Default - 360 (one rotation per sweep)
67
+ def sweep(degrees)
68
+ opts[:sweep] = degrees
69
+ end
70
+
71
+ # Set the callback invoked when the value changes.
72
+ def on_value(&blk)
73
+ opts[:on_value] = blk
74
+ end
75
+
76
+ # Set the callback invoked when the knob is depressed.
77
+ def on_push(&blk)
78
+ opts[:on_push] = blk
79
+ end
80
+
81
+ # Set the callback invoked when the knob is released.
82
+ def on_release(&blk)
83
+ opts[:on_release] = blk
84
+ end
85
+
86
+ end
87
+
88
+ class Context
89
+
90
+ def initialize(knob)
91
+ @knob = knob
92
+ end
93
+
94
+ # Returns the name of this logical knob (encoder).
95
+ def name
96
+ @knob.name
97
+ end
98
+
99
+ # Returns the value of this knob.
100
+ def value
101
+ @knob.typed_value
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,68 @@
1
+ module Arcenciel
2
+ module Surfaces
3
+ class Controller
4
+ include Logging
5
+
6
+ attr_reader :name
7
+ attr_reader :knobs
8
+
9
+ attr_reader :device
10
+
11
+ def self.from_dsl(&blk)
12
+ new(DSL.eval(&blk))
13
+ end
14
+
15
+ def initialize(options)
16
+ @name = options[:name] || default_name
17
+ @knobs = options[:knobs] || []
18
+
19
+ @device = nil
20
+ end
21
+
22
+ def assigned?
23
+ !!@device
24
+ end
25
+
26
+ def assign!(device)
27
+ log_info "Assigning controller '#{name}' to device..."
28
+
29
+ @device = device
30
+ knobs.each_with_index do |k, i|
31
+ k.assign!(device, i)
32
+ k.confirm!
33
+ end
34
+
35
+ device.attach!(self)
36
+ device.validate!
37
+
38
+ knobs.each do |k|
39
+ k.first_update!
40
+ end
41
+
42
+ log_info "Assigned controller '#{name}'."
43
+ end
44
+
45
+ def unassign!
46
+ @device = nil
47
+ knobs.each(&:unassign!)
48
+ log_warn "Controller '#{name}' is unassigned."
49
+ end
50
+
51
+ def on_delta(index, delta)
52
+ return unless index < knobs.size
53
+ knobs[index].on_delta(delta)
54
+ end
55
+
56
+ def on_key(index, state)
57
+ return unless index < knobs.size
58
+ knobs[index].on_key(state)
59
+ end
60
+
61
+ private
62
+
63
+ def default_name
64
+ 'Arc'
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,211 @@
1
+ require 'io/console'
2
+
3
+ module Arcenciel
4
+ module Surfaces
5
+ class Knob
6
+ include Logging
7
+
8
+ DEFAULT_OPTIONS = {
9
+ min: 0,
10
+ max: 100,
11
+ sweep: 360,
12
+ type: :float
13
+ }
14
+
15
+ attr_reader :name
16
+ attr_reader :min
17
+ attr_reader :max
18
+ attr_reader :type
19
+ attr_reader :sweep
20
+
21
+ attr_reader :on_value
22
+ attr_reader :on_push
23
+ attr_reader :on_release
24
+
25
+ attr_reader :value
26
+ attr_reader :counter
27
+ attr_reader :precision
28
+ attr_reader :depressed
29
+
30
+ attr_reader :context
31
+ attr_reader :device
32
+ attr_reader :index
33
+
34
+ def self.from_dsl(&blk)
35
+ new(DSL.eval(&blk))
36
+ end
37
+
38
+ def initialize(options)
39
+ options = DEFAULT_OPTIONS.merge(options)
40
+
41
+ @name = options[:name]
42
+ @min = options[:min]
43
+ @max = options[:max]
44
+ @type = options[:type]
45
+ @sweep = options[:sweep]
46
+ @value = options[:initial]
47
+
48
+ @on_value = options[:on_value]
49
+ @on_push = options[:on_push]
50
+ @on_release = options[:on_release]
51
+
52
+ @name ||= default_name
53
+ @value ||= @min
54
+
55
+ @precision = precision_for_sweep(sweep)
56
+ @counter = counter_for_value(@value)
57
+ @depressed = false
58
+
59
+ @context = Context.new(self)
60
+ @device = nil
61
+ @index = nil
62
+
63
+ clamp_counter
64
+ update_value
65
+ trigger_value
66
+ end
67
+
68
+ def typed_value
69
+ integer_type? ?
70
+ value.to_i :
71
+ value.to_f
72
+ end
73
+
74
+ def assigned?
75
+ !!@device
76
+ end
77
+
78
+ def assign!(device, index)
79
+ @device = device
80
+ @index = index
81
+ end
82
+
83
+ def unassign!
84
+ @device = nil
85
+ @index = nil
86
+ end
87
+
88
+ def confirm!
89
+ start_chaser
90
+
91
+ log_info "Illuminated knob '#{name}' (#{index}). Press any key."
92
+ wait_for_key
93
+
94
+ stop_chaser
95
+ ring_clear
96
+ end
97
+
98
+ def first_update!
99
+ update_ring
100
+ end
101
+
102
+ def on_delta(delta)
103
+ @counter += delta
104
+ clamp_counter
105
+
106
+ update_value
107
+ update_ring
108
+ trigger_value
109
+ end
110
+
111
+ def on_key(state)
112
+ case state
113
+ when 0
114
+ @depressed = false
115
+ trigger_release
116
+ when 1
117
+ @depressed = true
118
+ trigger_push
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def default_name
125
+ 'Unnamed'
126
+ end
127
+
128
+ def integer_type?
129
+ type == :integer
130
+ end
131
+
132
+ def fraction_for_counter(x)
133
+ x / precision.to_f
134
+ end
135
+
136
+ def value_for_counter(x)
137
+ f = fraction_for_counter(x)
138
+ (max - min) * f + min
139
+ end
140
+
141
+ def precision_for_sweep(x)
142
+ (256 / 360.0 * x).to_i
143
+ end
144
+
145
+ def counter_for_value(x)
146
+ ((x - min) / (max - min).to_f * precision.to_f).to_i
147
+ end
148
+
149
+ def clamp_counter
150
+ @counter = 0 if @counter < 0
151
+ @counter = precision if @counter > precision
152
+ end
153
+
154
+ def update_value
155
+ @value = value_for_counter(counter)
156
+ end
157
+
158
+ def update_ring
159
+ f = fraction_for_counter(counter)
160
+ ring_fraction(f)
161
+ end
162
+
163
+ def trigger_value
164
+ on_value &&
165
+ context.instance_exec(typed_value, &on_value)
166
+ end
167
+
168
+ def trigger_push
169
+ on_push &&
170
+ context.instance_exec(&on_push)
171
+ end
172
+
173
+ def trigger_release
174
+ on_release &&
175
+ context.instance_exec(&on_release)
176
+ end
177
+
178
+ def ring_clear
179
+ device.ring_clear(index)
180
+ end
181
+
182
+ def ring_fraction(x)
183
+ units = 64 * x
184
+ count = units.to_i
185
+ subpixel = units - units.to_i
186
+ final_level = (15 * subpixel).to_i
187
+
188
+ levels = []
189
+ (0...count).each { levels << 15 }
190
+ levels << final_level if levels.size < 64
191
+ (levels.size...64).each { levels << 0 }
192
+
193
+ device.ring_map(index, levels)
194
+ end
195
+
196
+ def start_chaser
197
+ @chaser = Chaser.new(device, index)
198
+ @chaser.start!
199
+ end
200
+
201
+ def stop_chaser
202
+ @chaser.stop!
203
+ @chaser = nil
204
+ end
205
+
206
+ def wait_for_key
207
+ $stdin.noecho(&:gets)
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,3 @@
1
+ require 'arcenciel/utility/logging'
2
+ require 'arcenciel/utility/dsl_base'
3
+ require 'arcenciel/utility/chaser'
@@ -0,0 +1,93 @@
1
+ require 'thread'
2
+
3
+ module Arcenciel
4
+ class Chaser
5
+ MAX_LEVEL = 13
6
+
7
+ SEQUENCE = (1..5).to_a.reverse.map do |i|
8
+ (MAX_LEVEL * 1.0 / (i * i)).to_i
9
+ end
10
+
11
+ attr_reader :device
12
+ attr_reader :index
13
+
14
+ def self.position
15
+ @position ||= 0
16
+ end
17
+
18
+ def self.advance
19
+ @position ||= 0
20
+ @position += 1
21
+ @position %= 64
22
+ end
23
+
24
+ def initialize(device, index)
25
+ @device = device
26
+ @index = index
27
+
28
+ @running = false
29
+ @thread = nil
30
+ end
31
+
32
+ def running?
33
+ !!@running
34
+ end
35
+
36
+ def start!
37
+ return if running?
38
+ @running = true
39
+ ring_clear
40
+ run_async
41
+ end
42
+
43
+ def stop!
44
+ return if !running?
45
+ @running = false
46
+ join_async
47
+ ring_clear
48
+ end
49
+
50
+ private
51
+
52
+ def run_async
53
+ @thread = Thread.start do
54
+ run_loop
55
+ end
56
+ end
57
+
58
+ def join_async
59
+ @thread.join
60
+ @thread = nil
61
+ end
62
+
63
+ def run_loop
64
+ while running?
65
+ advance
66
+ update
67
+ sleep 0.02
68
+ end
69
+ end
70
+
71
+ def advance
72
+ self.class.advance
73
+ end
74
+
75
+ def update
76
+ pos = self.class.position
77
+ (0...4).each do |e|
78
+ SEQUENCE.each_with_index do |level, i|
79
+ x = (pos + 16 * e + i) % 64
80
+ ring_set(x, level)
81
+ end
82
+ end
83
+ end
84
+
85
+ def ring_clear
86
+ device.ring_clear(index)
87
+ end
88
+
89
+ def ring_set(x, level)
90
+ device.ring_set(index, x, level)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ module Arcenciel
2
+ class DSLBase
3
+ attr_reader :opts
4
+
5
+ def self.eval(&blk)
6
+ dsl = new
7
+ dsl.instance_eval(&blk)
8
+ dsl.opts
9
+ end
10
+
11
+ def initialize
12
+ @opts = {}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ require 'logger'
2
+ require 'colored'
3
+
4
+ module Arcenciel
5
+ module Logging
6
+
7
+ class << self
8
+ attr_writer :logger
9
+
10
+ def logger
11
+ @logger ||= DEFAULT_LOGGER
12
+ end
13
+ end
14
+
15
+ def logger
16
+ Logging.logger
17
+ end
18
+
19
+ def log_info(msg)
20
+ logger.info(msg)
21
+ end
22
+
23
+ def log_warn(msg)
24
+ logger.warn(msg)
25
+ end
26
+
27
+ def log_error(msg)
28
+ logger.error(msg)
29
+ end
30
+
31
+ class DefaultFormater
32
+ SEVERITY_CONFIG = {
33
+ 'INFO' => ['ARC', :blue],
34
+ 'WARN' => ['WARN', :red],
35
+ 'ERROR' => ['ERROR', :red],
36
+ 'FATAL' => ['FATAL', :red],
37
+ 'DEBUG' => ['DEBUG', :blue]
38
+ }
39
+
40
+ def call(severity, time, progname, msg)
41
+ tag, color = SEVERITY_CONFIG[severity]
42
+ colored_tag = Colored.colorize(tag, foreground: color)
43
+
44
+ string = ''
45
+ string += "[#{progname}] " if progname
46
+ string += "[#{colored_tag}] "
47
+ string += msg
48
+ string += "\n"
49
+
50
+ string
51
+ end
52
+ end
53
+
54
+ DEFAULT_LOGGER = Logger.new($stdout).tap do |l|
55
+ l.formatter = DefaultFormater.new
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Arcenciel
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arcenciel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nelson Gauthier
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-03-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: osc-ruby
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: colored
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.2.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.2.0
46
+ description: Physical knobs for virtual machines
47
+ email:
48
+ - nelson.gauthier@gmail.com
49
+ executables:
50
+ - arcenciel-demo
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - DEDICATION
56
+ - Gemfile
57
+ - README.md
58
+ - arcenciel.gemspec
59
+ - bin/arcenciel-demo
60
+ - docs/images/hero.jpg
61
+ - lib/arcenciel.rb
62
+ - lib/arcenciel/manager.rb
63
+ - lib/arcenciel/manager/device.rb
64
+ - lib/arcenciel/manager/hub.rb
65
+ - lib/arcenciel/surfaces.rb
66
+ - lib/arcenciel/surfaces/controller.rb
67
+ - lib/arcenciel/surfaces/knob.rb
68
+ - lib/arcenciel/utility.rb
69
+ - lib/arcenciel/utility/chaser.rb
70
+ - lib/arcenciel/utility/dsl_base.rb
71
+ - lib/arcenciel/utility/logging.rb
72
+ - lib/arcenciel/version.rb
73
+ homepage: https://github.com/nelgau/arcenciel
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.23
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Declarative Monome Arc microframework
97
+ test_files: []
98
+ has_rdoc: