arcenciel 0.0.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.
@@ -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: